aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore135
-rw-r--r--AUTHORS2
-rw-r--r--INSTALL2
-rw-r--r--Makefile.am1532
-rw-r--r--NEWS172
-rw-r--r--UPGRADING92
-rw-r--r--android/.gitignore1
-rw-r--r--android/AndroidManifest.xml24
-rwxr-xr-xandroid/build.py433
-rw-r--r--android/custom_rules.xml11
-rw-r--r--android/res/values/strings.xml5
-rw-r--r--android/src/Bridge.java30
-rw-r--r--android/src/Loader.java39
-rw-r--r--android/src/Main.java75
-rwxr-xr-xautogen.sh140
-rw-r--r--configure.ac444
-rw-r--r--doc/developer.xml50
-rw-r--r--doc/mpd.conf.583
-rw-r--r--doc/mpdconf.example32
-rw-r--r--doc/protocol.xml471
-rw-r--r--doc/user.xml1698
-rw-r--r--m4/ax_append_compile_flags.m46
-rw-r--r--m4/ax_append_link_flags.m46
-rw-r--r--m4/ax_boost_base.m4272
-rw-r--r--m4/ax_check_compile_flag.m48
-rw-r--r--m4/ax_check_link_flag.m48
-rw-r--r--m4/ax_pthread.m4332
-rw-r--r--m4/ax_require_defined.m437
-rw-r--r--m4/mpd_depends.m49
-rw-r--r--m4/mpd_func.m413
-rw-r--r--m4/pkg.m4117
-rw-r--r--m4/ucred.m416
-rw-r--r--mpd.service.in9
-rw-r--r--mpd.svg857
-rw-r--r--src/ArchiveDomain.cxx23
-rw-r--r--src/ArchiveDomain.hxx25
-rw-r--r--src/ArchiveFile.hxx58
-rw-r--r--src/ArchiveList.cxx90
-rw-r--r--src/ArchiveList.hxx47
-rw-r--r--src/ArchiveLookup.cxx104
-rw-r--r--src/ArchiveLookup.hxx45
-rw-r--r--src/ArchivePlugin.cxx39
-rw-r--r--src/ArchivePlugin.hxx62
-rw-r--r--src/ArchiveVisitor.hxx28
-rw-r--r--src/AudioConfig.cxx8
-rw-r--r--src/AudioConfig.hxx2
-rw-r--r--src/AudioFormat.cxx2
-rw-r--r--src/AudioFormat.hxx2
-rw-r--r--src/AudioParser.cxx2
-rw-r--r--src/AudioParser.hxx2
-rw-r--r--src/AvahiPoll.cxx151
-rw-r--r--src/AvahiPoll.hxx48
-rw-r--r--src/CheckAudioFormat.cxx2
-rw-r--r--src/CheckAudioFormat.hxx2
-rw-r--r--src/Chrono.hxx226
-rw-r--r--src/Client.cxx24
-rw-r--r--src/Client.hxx187
-rw-r--r--src/ClientEvent.cxx37
-rw-r--r--src/ClientExpire.cxx43
-rw-r--r--src/ClientFile.cxx70
-rw-r--r--src/ClientFile.hxx40
-rw-r--r--src/ClientGlobal.cxx46
-rw-r--r--src/ClientIdle.cxx75
-rw-r--r--src/ClientInternal.hxx39
-rw-r--r--src/ClientList.cxx56
-rw-r--r--src/ClientList.hxx64
-rw-r--r--src/ClientMessage.cxx41
-rw-r--r--src/ClientMessage.hxx52
-rw-r--r--src/ClientNew.cxx126
-rw-r--r--src/ClientProcess.cxx141
-rw-r--r--src/ClientRead.cxx76
-rw-r--r--src/ClientSubscribe.cxx92
-rw-r--r--src/ClientWrite.cxx61
-rw-r--r--src/CommandLine.cxx317
-rw-r--r--src/CommandLine.hxx12
-rw-r--r--src/Compiler.h2
-rw-r--r--src/ConfigData.cxx160
-rw-r--r--src/ConfigData.hxx134
-rw-r--r--src/ConfigDefaults.hxx26
-rw-r--r--src/ConfigError.cxx23
-rw-r--r--src/ConfigError.hxx25
-rw-r--r--src/ConfigFile.cxx272
-rw-r--r--src/ConfigFile.hxx30
-rw-r--r--src/ConfigGlobal.cxx177
-rw-r--r--src/ConfigGlobal.hxx95
-rw-r--r--src/ConfigOption.hxx90
-rw-r--r--src/ConfigParser.cxx40
-rw-r--r--src/ConfigParser.hxx26
-rw-r--r--src/ConfigPath.cxx132
-rw-r--r--src/ConfigPath.hxx29
-rw-r--r--src/ConfigTemplates.cxx96
-rw-r--r--src/ConfigTemplates.hxx33
-rw-r--r--src/CrossFade.cxx10
-rw-r--r--src/CrossFade.hxx5
-rw-r--r--src/Daemon.cxx247
-rw-r--r--src/Daemon.hxx91
-rw-r--r--src/DatabaseError.cxx24
-rw-r--r--src/DatabaseError.hxx37
-rw-r--r--src/DatabaseGlue.cxx161
-rw-r--r--src/DatabaseGlue.hxx59
-rw-r--r--src/DatabaseHelpers.cxx134
-rw-r--r--src/DatabaseHelpers.hxx42
-rw-r--r--src/DatabaseLock.cxx28
-rw-r--r--src/DatabaseLock.hxx97
-rw-r--r--src/DatabasePlaylist.cxx50
-rw-r--r--src/DatabasePlaylist.hxx34
-rw-r--r--src/DatabasePlugin.hxx156
-rw-r--r--src/DatabasePrint.cxx241
-rw-r--r--src/DatabasePrint.hxx55
-rw-r--r--src/DatabaseQueue.cxx54
-rw-r--r--src/DatabaseQueue.hxx31
-rw-r--r--src/DatabaseRegistry.cxx43
-rw-r--r--src/DatabaseRegistry.hxx37
-rw-r--r--src/DatabaseSave.cxx157
-rw-r--r--src/DatabaseSave.hxx35
-rw-r--r--src/DatabaseSelection.cxx49
-rw-r--r--src/DatabaseSelection.hxx60
-rw-r--r--src/DatabaseSimple.hxx74
-rw-r--r--src/DatabaseVisitor.hxx37
-rw-r--r--src/DecoderAPI.cxx585
-rw-r--r--src/DecoderAPI.hxx209
-rw-r--r--src/DecoderBuffer.cxx185
-rw-r--r--src/DecoderBuffer.hxx123
-rw-r--r--src/DecoderCommand.hxx32
-rw-r--r--src/DecoderControl.cxx145
-rw-r--r--src/DecoderControl.hxx395
-rw-r--r--src/DecoderError.cxx23
-rw-r--r--src/DecoderError.hxx25
-rw-r--r--src/DecoderInternal.cxx103
-rw-r--r--src/DecoderInternal.hxx117
-rw-r--r--src/DecoderList.cxx239
-rw-r--r--src/DecoderList.hxx92
-rw-r--r--src/DecoderPlugin.cxx48
-rw-r--r--src/DecoderPlugin.hxx181
-rw-r--r--src/DecoderPrint.cxx55
-rw-r--r--src/DecoderPrint.hxx28
-rw-r--r--src/DecoderThread.cxx461
-rw-r--r--src/DecoderThread.hxx28
-rw-r--r--src/DespotifyUtils.cxx154
-rw-r--r--src/DespotifyUtils.hxx71
-rw-r--r--src/DetachedSong.cxx73
-rw-r--r--src/DetachedSong.hxx226
-rw-r--r--src/Directory.cxx334
-rw-r--r--src/Directory.hxx263
-rw-r--r--src/DirectorySave.cxx171
-rw-r--r--src/DirectorySave.hxx35
-rw-r--r--src/EncoderAPI.hxx33
-rw-r--r--src/EncoderList.cxx64
-rw-r--r--src/EncoderList.hxx43
-rw-r--r--src/EncoderPlugin.hxx321
-rw-r--r--src/ExcludeList.cxx83
-rw-r--r--src/ExcludeList.hxx80
-rw-r--r--src/FilterConfig.cxx110
-rw-r--r--src/FilterConfig.hxx43
-rw-r--r--src/FilterInternal.hxx74
-rw-r--r--src/FilterPlugin.cxx59
-rw-r--r--src/FilterPlugin.hxx67
-rw-r--r--src/FilterRegistry.cxx44
-rw-r--r--src/FilterRegistry.hxx43
-rw-r--r--src/GlobalEvents.cxx3
-rw-r--r--src/GlobalEvents.hxx11
-rw-r--r--src/IOThread.cxx5
-rw-r--r--src/IOThread.hxx4
-rw-r--r--src/IcyMetaDataParser.cxx50
-rw-r--r--src/IcyMetaDataParser.hxx9
-rw-r--r--src/IcyMetaDataServer.cxx135
-rw-r--r--src/IcyMetaDataServer.hxx39
-rw-r--r--src/IdTable.hxx91
-rw-r--r--src/Idle.cxx17
-rw-r--r--src/Idle.hxx18
-rw-r--r--src/InotifyDomain.cxx23
-rw-r--r--src/InotifyDomain.hxx25
-rw-r--r--src/InotifyQueue.cxx90
-rw-r--r--src/InotifyQueue.hxx41
-rw-r--r--src/InotifySource.cxx114
-rw-r--r--src/InotifySource.hxx71
-rw-r--r--src/InotifyUpdate.cxx339
-rw-r--r--src/InotifyUpdate.hxx47
-rw-r--r--src/InputInit.cxx102
-rw-r--r--src/InputInit.hxx36
-rw-r--r--src/InputPlugin.hxx91
-rw-r--r--src/InputRegistry.cxx72
-rw-r--r--src/InputRegistry.hxx43
-rw-r--r--src/InputStream.cxx198
-rw-r--r--src/InputStream.hxx292
-rw-r--r--src/Instance.cxx72
-rw-r--r--src/Instance.hxx79
-rw-r--r--src/Listen.cxx47
-rw-r--r--src/Listen.hxx6
-rw-r--r--src/Log.cxx55
-rw-r--r--src/Log.hxx39
-rw-r--r--src/LogBackend.cxx230
-rw-r--r--src/LogBackend.hxx45
-rw-r--r--src/LogInit.cxx257
-rw-r--r--src/LogInit.hxx2
-rw-r--r--src/LogLevel.hxx59
-rw-r--r--src/LogV.hxx4
-rw-r--r--src/Main.cxx453
-rw-r--r--src/Main.hxx14
-rw-r--r--src/Mapper.cxx187
-rw-r--r--src/Mapper.hxx80
-rw-r--r--src/MemorySongEnumerator.cxx32
-rw-r--r--src/MemorySongEnumerator.hxx38
-rw-r--r--src/MixRampInfo.hxx2
-rw-r--r--src/MixerAll.cxx175
-rw-r--r--src/MixerAll.hxx64
-rw-r--r--src/MixerControl.cxx162
-rw-r--r--src/MixerControl.hxx60
-rw-r--r--src/MixerInternal.hxx59
-rw-r--r--src/MixerList.hxx35
-rw-r--r--src/MixerPlugin.hxx95
-rw-r--r--src/MixerType.cxx39
-rw-r--r--src/MixerType.hxx47
-rw-r--r--src/MusicBuffer.cxx6
-rw-r--r--src/MusicBuffer.hxx14
-rw-r--r--src/MusicChunk.cxx27
-rw-r--r--src/MusicChunk.hxx26
-rw-r--r--src/MusicPipe.cxx16
-rw-r--r--src/MusicPipe.hxx20
-rw-r--r--src/OutputAPI.hxx29
-rw-r--r--src/OutputAll.cxx594
-rw-r--r--src/OutputAll.hxx174
-rw-r--r--src/OutputCommand.cxx113
-rw-r--r--src/OutputCommand.hxx51
-rw-r--r--src/OutputControl.cxx336
-rw-r--r--src/OutputControl.hxx90
-rw-r--r--src/OutputError.cxx23
-rw-r--r--src/OutputError.hxx25
-rw-r--r--src/OutputFinish.cxx51
-rw-r--r--src/OutputInit.cxx331
-rw-r--r--src/OutputInternal.hxx297
-rw-r--r--src/OutputList.cxx100
-rw-r--r--src/OutputList.hxx33
-rw-r--r--src/OutputPlugin.cxx109
-rw-r--r--src/OutputPlugin.hxx202
-rw-r--r--src/OutputPrint.cxx45
-rw-r--r--src/OutputPrint.hxx34
-rw-r--r--src/OutputState.cxx94
-rw-r--r--src/OutputState.hxx44
-rw-r--r--src/OutputThread.cxx688
-rw-r--r--src/OutputThread.hxx27
-rw-r--r--src/Page.cxx70
-rw-r--r--src/Page.hxx104
-rw-r--r--src/Partition.cxx41
-rw-r--r--src/Partition.hxx57
-rw-r--r--src/Permission.cxx12
-rw-r--r--src/Permission.hxx2
-rw-r--r--src/PlayerControl.cxx46
-rw-r--r--src/PlayerControl.hxx69
-rw-r--r--src/PlayerListener.hxx36
-rw-r--r--src/PlayerThread.cxx191
-rw-r--r--src/PlayerThread.hxx8
-rw-r--r--src/Playlist.cxx349
-rw-r--r--src/Playlist.hxx284
-rw-r--r--src/PlaylistAny.cxx70
-rw-r--r--src/PlaylistAny.hxx41
-rw-r--r--src/PlaylistControl.cxx269
-rw-r--r--src/PlaylistDatabase.cxx19
-rw-r--r--src/PlaylistDatabase.hxx7
-rw-r--r--src/PlaylistEdit.cxx463
-rw-r--r--src/PlaylistError.cxx2
-rw-r--r--src/PlaylistError.hxx2
-rw-r--r--src/PlaylistFile.cxx102
-rw-r--r--src/PlaylistFile.hxx12
-rw-r--r--src/PlaylistGlobal.cxx3
-rw-r--r--src/PlaylistGlobal.hxx2
-rw-r--r--src/PlaylistInfo.hxx63
-rw-r--r--src/PlaylistMapper.cxx102
-rw-r--r--src/PlaylistMapper.hxx40
-rw-r--r--src/PlaylistPlugin.hxx109
-rw-r--r--src/PlaylistPrint.cxx83
-rw-r--r--src/PlaylistPrint.hxx14
-rw-r--r--src/PlaylistQueue.cxx90
-rw-r--r--src/PlaylistQueue.hxx59
-rw-r--r--src/PlaylistRegistry.cxx342
-rw-r--r--src/PlaylistRegistry.hxx83
-rw-r--r--src/PlaylistSave.cxx86
-rw-r--r--src/PlaylistSave.hxx16
-rw-r--r--src/PlaylistSong.cxx176
-rw-r--r--src/PlaylistSong.hxx37
-rw-r--r--src/PlaylistState.cxx244
-rw-r--r--src/PlaylistState.hxx52
-rw-r--r--src/PlaylistUpdate.cxx80
-rw-r--r--src/PlaylistVector.cxx67
-rw-r--r--src/PlaylistVector.hxx56
-rw-r--r--src/Queue.cxx490
-rw-r--r--src/Queue.hxx378
-rw-r--r--src/QueuePrint.cxx107
-rw-r--r--src/QueuePrint.hxx54
-rw-r--r--src/QueueSave.cxx133
-rw-r--r--src/QueueSave.hxx42
-rw-r--r--src/ReplayGainConfig.cxx8
-rw-r--r--src/ReplayGainConfig.hxx2
-rw-r--r--src/ReplayGainInfo.cxx2
-rw-r--r--src/ReplayGainInfo.hxx2
-rw-r--r--src/SignalHandlers.cxx82
-rw-r--r--src/SignalHandlers.hxx31
-rw-r--r--src/Song.cxx183
-rw-r--r--src/Song.hxx152
-rw-r--r--src/SongEnumerator.hxx41
-rw-r--r--src/SongFilter.cxx148
-rw-r--r--src/SongFilter.hxx26
-rw-r--r--src/SongLoader.cxx111
-rw-r--r--src/SongLoader.hxx77
-rw-r--r--src/SongPointer.hxx63
-rw-r--r--src/SongPrint.cxx112
-rw-r--r--src/SongPrint.hxx15
-rw-r--r--src/SongSave.cxx86
-rw-r--r--src/SongSave.hxx15
-rw-r--r--src/SongSort.cxx124
-rw-r--r--src/SongSort.hxx28
-rw-r--r--src/SongSticker.cxx135
-rw-r--r--src/SongSticker.hxx86
-rw-r--r--src/SongUpdate.cxx142
-rw-r--r--src/StateFile.cxx72
-rw-r--r--src/StateFile.hxx18
-rw-r--r--src/Stats.cxx104
-rw-r--r--src/Stats.hxx7
-rw-r--r--src/StickerDatabase.cxx604
-rw-r--r--src/StickerDatabase.hxx161
-rw-r--r--src/StickerPrint.cxx44
-rw-r--r--src/StickerPrint.hxx38
-rw-r--r--src/TagFile.cxx104
-rw-r--r--src/TagFile.hxx5
-rw-r--r--src/TagPrint.cxx31
-rw-r--r--src/TagPrint.hxx12
-rw-r--r--src/TagSave.cxx20
-rw-r--r--src/TagSave.hxx7
-rw-r--r--src/TagStream.cxx79
-rw-r--r--src/TagStream.hxx41
-rw-r--r--src/TextFile.cxx77
-rw-r--r--src/TextFile.hxx61
-rw-r--r--src/TextInputStream.cxx79
-rw-r--r--src/TextInputStream.hxx57
-rw-r--r--src/TimePrint.cxx4
-rw-r--r--src/TimePrint.hxx2
-rw-r--r--src/Timer.cxx82
-rw-r--r--src/Timer.hxx49
-rw-r--r--src/UpdateArchive.cxx169
-rw-r--r--src/UpdateArchive.hxx51
-rw-r--r--src/UpdateContainer.cxx127
-rw-r--r--src/UpdateContainer.hxx36
-rw-r--r--src/UpdateDatabase.cxx104
-rw-r--r--src/UpdateDatabase.hxx50
-rw-r--r--src/UpdateDomain.cxx23
-rw-r--r--src/UpdateDomain.hxx25
-rw-r--r--src/UpdateGlue.cxx181
-rw-r--r--src/UpdateGlue.hxx43
-rw-r--r--src/UpdateIO.cxx113
-rw-r--r--src/UpdateIO.hxx50
-rw-r--r--src/UpdateInternal.hxx28
-rw-r--r--src/UpdateQueue.cxx49
-rw-r--r--src/UpdateQueue.hxx48
-rw-r--r--src/UpdateRemove.cxx94
-rw-r--r--src/UpdateRemove.hxx38
-rw-r--r--src/UpdateSong.cxx116
-rw-r--r--src/UpdateSong.hxx34
-rw-r--r--src/UpdateWalk.cxx488
-rw-r--r--src/UpdateWalk.hxx37
-rw-r--r--src/Volume.cxx140
-rw-r--r--src/Volume.hxx51
-rw-r--r--src/ZeroconfAvahi.cxx278
-rw-r--r--src/ZeroconfAvahi.hxx31
-rw-r--r--src/ZeroconfBonjour.cxx109
-rw-r--r--src/ZeroconfBonjour.hxx31
-rw-r--r--src/ZeroconfGlue.cxx83
-rw-r--r--src/ZeroconfGlue.hxx47
-rw-r--r--src/ZeroconfInternal.hxx26
-rw-r--r--src/android/Context.cxx43
-rw-r--r--src/android/Context.hxx35
-rw-r--r--src/android/Environment.cxx85
-rw-r--r--src/android/Environment.hxx43
-rw-r--r--src/archive/ArchiveDomain.cxx23
-rw-r--r--src/archive/ArchiveDomain.hxx25
-rw-r--r--src/archive/ArchiveFile.hxx61
-rw-r--r--src/archive/ArchiveList.cxx90
-rw-r--r--src/archive/ArchiveList.hxx47
-rw-r--r--src/archive/ArchiveLookup.cxx103
-rw-r--r--src/archive/ArchiveLookup.hxx45
-rw-r--r--src/archive/ArchivePlugin.cxx40
-rw-r--r--src/archive/ArchivePlugin.hxx61
-rw-r--r--src/archive/ArchiveVisitor.hxx28
-rw-r--r--src/archive/Bzip2ArchivePlugin.cxx294
-rw-r--r--src/archive/Bzip2ArchivePlugin.hxx25
-rw-r--r--src/archive/Iso9660ArchivePlugin.cxx260
-rw-r--r--src/archive/Iso9660ArchivePlugin.hxx25
-rw-r--r--src/archive/ZzipArchivePlugin.cxx226
-rw-r--r--src/archive/ZzipArchivePlugin.hxx25
-rw-r--r--src/archive/plugins/Bzip2ArchivePlugin.cxx259
-rw-r--r--src/archive/plugins/Bzip2ArchivePlugin.hxx27
-rw-r--r--src/archive/plugins/Iso9660ArchivePlugin.cxx238
-rw-r--r--src/archive/plugins/Iso9660ArchivePlugin.hxx27
-rw-r--r--src/archive/plugins/ZzipArchivePlugin.cxx193
-rw-r--r--src/archive/plugins/ZzipArchivePlugin.hxx27
-rw-r--r--src/check.h2
-rw-r--r--src/client/Client.cxx42
-rw-r--r--src/client/Client.hxx226
-rw-r--r--src/client/ClientEvent.cxx37
-rw-r--r--src/client/ClientExpire.cxx43
-rw-r--r--src/client/ClientFile.cxx64
-rw-r--r--src/client/ClientGlobal.cxx45
-rw-r--r--src/client/ClientIdle.cxx75
-rw-r--r--src/client/ClientInternal.hxx39
-rw-r--r--src/client/ClientList.cxx49
-rw-r--r--src/client/ClientList.hxx65
-rw-r--r--src/client/ClientMessage.cxx41
-rw-r--r--src/client/ClientMessage.hxx58
-rw-r--r--src/client/ClientNew.cxx117
-rw-r--r--src/client/ClientProcess.cxx141
-rw-r--r--src/client/ClientRead.cxx75
-rw-r--r--src/client/ClientSubscribe.cxx87
-rw-r--r--src/client/ClientWrite.cxx61
-rw-r--r--src/command/AllCommands.cxx66
-rw-r--r--src/command/AllCommands.hxx2
-rw-r--r--src/command/CommandError.cxx17
-rw-r--r--src/command/CommandError.hxx2
-rw-r--r--src/command/CommandListBuilder.cxx4
-rw-r--r--src/command/CommandListBuilder.hxx2
-rw-r--r--src/command/CommandResult.hxx2
-rw-r--r--src/command/DatabaseCommands.cxx168
-rw-r--r--src/command/DatabaseCommands.hxx25
-rw-r--r--src/command/FileCommands.cxx180
-rw-r--r--src/command/FileCommands.hxx7
-rw-r--r--src/command/MessageCommands.cxx29
-rw-r--r--src/command/MessageCommands.hxx12
-rw-r--r--src/command/NeighborCommands.cxx59
-rw-r--r--src/command/NeighborCommands.hxx36
-rw-r--r--src/command/OtherCommands.cxx280
-rw-r--r--src/command/OtherCommands.hxx35
-rw-r--r--src/command/OutputCommands.cxx32
-rw-r--r--src/command/OutputCommands.hxx10
-rw-r--r--src/command/PlayerCommands.cxx95
-rw-r--r--src/command/PlayerCommands.hxx44
-rw-r--r--src/command/PlaylistCommands.cxx93
-rw-r--r--src/command/PlaylistCommands.hxx24
-rw-r--r--src/command/QueueCommands.cxx216
-rw-r--r--src/command/QueueCommands.hxx43
-rw-r--r--src/command/StickerCommands.cxx60
-rw-r--r--src/command/StickerCommands.hxx4
-rw-r--r--src/command/StorageCommands.cxx297
-rw-r--r--src/command/StorageCommands.hxx43
-rw-r--r--src/command/TagCommands.cxx78
-rw-r--r--src/command/TagCommands.hxx33
-rw-r--r--src/config/ConfigData.cxx159
-rw-r--r--src/config/ConfigData.hxx134
-rw-r--r--src/config/ConfigDefaults.hxx26
-rw-r--r--src/config/ConfigError.cxx23
-rw-r--r--src/config/ConfigError.hxx25
-rw-r--r--src/config/ConfigFile.cxx269
-rw-r--r--src/config/ConfigFile.hxx30
-rw-r--r--src/config/ConfigGlobal.cxx190
-rw-r--r--src/config/ConfigGlobal.hxx97
-rw-r--r--src/config/ConfigOption.hxx92
-rw-r--r--src/config/ConfigParser.cxx40
-rw-r--r--src/config/ConfigParser.hxx26
-rw-r--r--src/config/ConfigPath.cxx131
-rw-r--r--src/config/ConfigPath.hxx29
-rw-r--r--src/config/ConfigTemplates.cxx98
-rw-r--r--src/config/ConfigTemplates.hxx31
-rw-r--r--src/cue/CueParser.cxx321
-rw-r--r--src/cue/CueParser.hxx133
-rw-r--r--src/db/Configured.cxx72
-rw-r--r--src/db/Configured.hxx39
-rw-r--r--src/db/Count.cxx151
-rw-r--r--src/db/Count.hxx39
-rw-r--r--src/db/DatabaseError.cxx24
-rw-r--r--src/db/DatabaseError.hxx39
-rw-r--r--src/db/DatabaseGlue.cxx45
-rw-r--r--src/db/DatabaseGlue.hxx40
-rw-r--r--src/db/DatabaseListener.hxx46
-rw-r--r--src/db/DatabaseLock.cxx27
-rw-r--r--src/db/DatabaseLock.hxx97
-rw-r--r--src/db/DatabasePlaylist.cxx52
-rw-r--r--src/db/DatabasePlaylist.hxx37
-rw-r--r--src/db/DatabasePlugin.hxx58
-rw-r--r--src/db/DatabasePrint.cxx221
-rw-r--r--src/db/DatabasePrint.hxx45
-rw-r--r--src/db/DatabaseQueue.cxx53
-rw-r--r--src/db/DatabaseQueue.hxx31
-rw-r--r--src/db/DatabaseSong.cxx55
-rw-r--r--src/db/DatabaseSong.hxx50
-rw-r--r--src/db/Helpers.cxx98
-rw-r--r--src/db/Helpers.hxx32
-rw-r--r--src/db/Interface.hxx138
-rw-r--r--src/db/LightDirectory.hxx61
-rw-r--r--src/db/LightSong.cxx35
-rw-r--r--src/db/LightSong.hxx93
-rw-r--r--src/db/PlaylistInfo.hxx63
-rw-r--r--src/db/PlaylistVector.cxx66
-rw-r--r--src/db/PlaylistVector.hxx56
-rw-r--r--src/db/ProxyDatabasePlugin.cxx681
-rw-r--r--src/db/ProxyDatabasePlugin.hxx27
-rw-r--r--src/db/Registry.cxx48
-rw-r--r--src/db/Registry.hxx37
-rw-r--r--src/db/Selection.cxx49
-rw-r--r--src/db/Selection.hxx60
-rw-r--r--src/db/SimpleDatabasePlugin.cxx323
-rw-r--r--src/db/SimpleDatabasePlugin.hxx99
-rw-r--r--src/db/Stats.hxx53
-rw-r--r--src/db/UniqueTags.cxx59
-rw-r--r--src/db/UniqueTags.hxx38
-rw-r--r--src/db/Uri.hxx29
-rw-r--r--src/db/Visitor.hxx38
-rw-r--r--src/db/plugins/LazyDatabase.cxx108
-rw-r--r--src/db/plugins/LazyDatabase.hxx69
-rw-r--r--src/db/plugins/ProxyDatabasePlugin.cxx851
-rw-r--r--src/db/plugins/ProxyDatabasePlugin.hxx27
-rw-r--r--src/db/plugins/simple/DatabaseSave.cxx160
-rw-r--r--src/db/plugins/simple/DatabaseSave.hxx34
-rw-r--r--src/db/plugins/simple/Directory.cxx277
-rw-r--r--src/db/plugins/simple/Directory.hxx285
-rw-r--r--src/db/plugins/simple/DirectorySave.cxx207
-rw-r--r--src/db/plugins/simple/DirectorySave.hxx34
-rw-r--r--src/db/plugins/simple/Mount.cxx96
-rw-r--r--src/db/plugins/simple/Mount.hxx36
-rw-r--r--src/db/plugins/simple/PrefixedLightSong.hxx41
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.cxx541
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.hxx149
-rw-r--r--src/db/plugins/simple/Song.cxx112
-rw-r--r--src/db/plugins/simple/Song.hxx130
-rw-r--r--src/db/plugins/simple/SongSort.cxx108
-rw-r--r--src/db/plugins/simple/SongSort.hxx30
-rw-r--r--src/db/plugins/upnp/ContentDirectoryService.cxx205
-rw-r--r--src/db/plugins/upnp/Directory.cxx263
-rw-r--r--src/db/plugins/upnp/Directory.hxx66
-rw-r--r--src/db/plugins/upnp/Object.cxx25
-rw-r--r--src/db/plugins/upnp/Object.hxx85
-rw-r--r--src/db/plugins/upnp/Tags.cxx33
-rw-r--r--src/db/plugins/upnp/Tags.hxx28
-rw-r--r--src/db/plugins/upnp/UpnpDatabasePlugin.cxx783
-rw-r--r--src/db/plugins/upnp/UpnpDatabasePlugin.hxx27
-rw-r--r--src/db/update/Archive.cxx167
-rw-r--r--src/db/update/Container.cxx132
-rw-r--r--src/db/update/Editor.cxx119
-rw-r--r--src/db/update/Editor.hxx71
-rw-r--r--src/db/update/ExcludeList.cxx93
-rw-r--r--src/db/update/ExcludeList.hxx92
-rw-r--r--src/db/update/InotifyDomain.cxx23
-rw-r--r--src/db/update/InotifyDomain.hxx25
-rw-r--r--src/db/update/InotifyQueue.cxx89
-rw-r--r--src/db/update/InotifyQueue.hxx46
-rw-r--r--src/db/update/InotifySource.cxx116
-rw-r--r--src/db/update/InotifySource.hxx72
-rw-r--r--src/db/update/InotifyUpdate.cxx344
-rw-r--r--src/db/update/InotifyUpdate.hxx37
-rw-r--r--src/db/update/Queue.cxx67
-rw-r--r--src/db/update/Queue.hxx77
-rw-r--r--src/db/update/Remove.cxx68
-rw-r--r--src/db/update/Remove.hxx62
-rw-r--r--src/db/update/Service.cxx279
-rw-r--r--src/db/update/Service.hxx116
-rw-r--r--src/db/update/UpdateDomain.cxx23
-rw-r--r--src/db/update/UpdateDomain.hxx25
-rw-r--r--src/db/update/UpdateIO.cxx107
-rw-r--r--src/db/update/UpdateIO.hxx62
-rw-r--r--src/db/update/UpdateSong.cxx103
-rw-r--r--src/db/update/Walk.cxx491
-rw-r--r--src/db/update/Walk.hxx157
-rw-r--r--src/decoder/AdPlugDecoderPlugin.cxx141
-rw-r--r--src/decoder/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/AudiofileDecoderPlugin.cxx283
-rw-r--r--src/decoder/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/DecoderAPI.cxx641
-rw-r--r--src/decoder/DecoderAPI.hxx237
-rw-r--r--src/decoder/DecoderBuffer.cxx71
-rw-r--r--src/decoder/DecoderBuffer.hxx117
-rw-r--r--src/decoder/DecoderCommand.hxx32
-rw-r--r--src/decoder/DecoderControl.cxx139
-rw-r--r--src/decoder/DecoderControl.hxx395
-rw-r--r--src/decoder/DecoderError.cxx23
-rw-r--r--src/decoder/DecoderError.hxx25
-rw-r--r--src/decoder/DecoderInternal.cxx104
-rw-r--r--src/decoder/DecoderInternal.hxx126
-rw-r--r--src/decoder/DecoderList.cxx163
-rw-r--r--src/decoder/DecoderList.hxx89
-rw-r--r--src/decoder/DecoderPlugin.cxx48
-rw-r--r--src/decoder/DecoderPlugin.hxx186
-rw-r--r--src/decoder/DecoderPrint.cxx55
-rw-r--r--src/decoder/DecoderPrint.hxx28
-rw-r--r--src/decoder/DecoderThread.cxx509
-rw-r--r--src/decoder/DecoderThread.hxx28
-rw-r--r--src/decoder/DsdLib.cxx157
-rw-r--r--src/decoder/DsdLib.hxx78
-rw-r--r--src/decoder/DsdiffDecoderPlugin.cxx522
-rw-r--r--src/decoder/DsdiffDecoderPlugin.hxx25
-rw-r--r--src/decoder/DsfDecoderPlugin.cxx361
-rw-r--r--src/decoder/DsfDecoderPlugin.hxx25
-rw-r--r--src/decoder/FaadDecoderPlugin.cxx490
-rw-r--r--src/decoder/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegDecoderPlugin.cxx741
-rw-r--r--src/decoder/FfmpegDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegMetaData.cxx76
-rw-r--r--src/decoder/FfmpegMetaData.hxx38
-rw-r--r--src/decoder/FlacCommon.cxx194
-rw-r--r--src/decoder/FlacCommon.hxx94
-rw-r--r--src/decoder/FlacDecoderPlugin.cxx387
-rw-r--r--src/decoder/FlacDecoderPlugin.h26
-rw-r--r--src/decoder/FlacDomain.cxx24
-rw-r--r--src/decoder/FlacDomain.hxx27
-rw-r--r--src/decoder/FlacIOHandle.cxx114
-rw-r--r--src/decoder/FlacIOHandle.hxx45
-rw-r--r--src/decoder/FlacInput.cxx154
-rw-r--r--src/decoder/FlacInput.hxx75
-rw-r--r--src/decoder/FlacMetadata.cxx245
-rw-r--r--src/decoder/FlacMetadata.hxx141
-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.cxx295
-rw-r--r--src/decoder/GmeDecoderPlugin.hxx25
-rw-r--r--src/decoder/MadDecoderPlugin.cxx1155
-rw-r--r--src/decoder/MadDecoderPlugin.hxx25
-rw-r--r--src/decoder/MikmodDecoderPlugin.cxx248
-rw-r--r--src/decoder/MikmodDecoderPlugin.hxx25
-rw-r--r--src/decoder/ModplugDecoderPlugin.cxx215
-rw-r--r--src/decoder/ModplugDecoderPlugin.hxx25
-rw-r--r--src/decoder/MpcdecDecoderPlugin.cxx280
-rw-r--r--src/decoder/MpcdecDecoderPlugin.hxx25
-rw-r--r--src/decoder/Mpg123DecoderPlugin.cxx256
-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.cxx68
-rw-r--r--src/decoder/OggFind.hxx57
-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.cxx489
-rw-r--r--src/decoder/OpusDecoderPlugin.h25
-rw-r--r--src/decoder/OpusDomain.cxx24
-rw-r--r--src/decoder/OpusDomain.hxx27
-rw-r--r--src/decoder/OpusHead.cxx44
-rw-r--r--src/decoder/OpusHead.hxx30
-rw-r--r--src/decoder/OpusReader.hxx101
-rw-r--r--src/decoder/OpusTags.cxx102
-rw-r--r--src/decoder/OpusTags.hxx34
-rw-r--r--src/decoder/PcmDecoderPlugin.cxx114
-rw-r--r--src/decoder/PcmDecoderPlugin.hxx33
-rw-r--r--src/decoder/SidplayDecoderPlugin.cxx435
-rw-r--r--src/decoder/SidplayDecoderPlugin.hxx25
-rw-r--r--src/decoder/SndfileDecoderPlugin.cxx273
-rw-r--r--src/decoder/SndfileDecoderPlugin.hxx25
-rw-r--r--src/decoder/VorbisComments.cxx149
-rw-r--r--src/decoder/VorbisComments.hxx39
-rw-r--r--src/decoder/VorbisDecoderPlugin.cxx352
-rw-r--r--src/decoder/VorbisDecoderPlugin.h25
-rw-r--r--src/decoder/VorbisDomain.cxx24
-rw-r--r--src/decoder/VorbisDomain.hxx27
-rw-r--r--src/decoder/WavpackDecoderPlugin.cxx571
-rw-r--r--src/decoder/WavpackDecoderPlugin.hxx25
-rw-r--r--src/decoder/WildmidiDecoderPlugin.cxx158
-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/plugins/AdPlugDecoderPlugin.cxx148
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.cxx297
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/DsdLib.cxx151
-rw-r--r--src/decoder/plugins/DsdLib.hxx86
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx510
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx383
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.cxx450
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx779
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FfmpegMetaData.cxx76
-rw-r--r--src/decoder/plugins/FfmpegMetaData.hxx38
-rw-r--r--src/decoder/plugins/FlacCommon.cxx193
-rw-r--r--src/decoder/plugins/FlacCommon.hxx93
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.cxx387
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.h26
-rw-r--r--src/decoder/plugins/FlacDomain.cxx24
-rw-r--r--src/decoder/plugins/FlacDomain.hxx27
-rw-r--r--src/decoder/plugins/FlacIOHandle.cxx134
-rw-r--r--src/decoder/plugins/FlacIOHandle.hxx45
-rw-r--r--src/decoder/plugins/FlacInput.cxx154
-rw-r--r--src/decoder/plugins/FlacInput.hxx75
-rw-r--r--src/decoder/plugins/FlacMetadata.cxx179
-rw-r--r--src/decoder/plugins/FlacMetadata.hxx131
-rw-r--r--src/decoder/plugins/FlacPcm.cxx110
-rw-r--r--src/decoder/plugins/FlacPcm.hxx33
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx226
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.cxx298
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.cxx1104
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.cxx250
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.cxx217
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.cxx280
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.cxx341
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/OggCodec.cxx51
-rw-r--r--src/decoder/plugins/OggCodec.hxx40
-rw-r--r--src/decoder/plugins/OggFind.cxx70
-rw-r--r--src/decoder/plugins/OggFind.hxx57
-rw-r--r--src/decoder/plugins/OggSyncState.hxx78
-rw-r--r--src/decoder/plugins/OggUtil.cxx118
-rw-r--r--src/decoder/plugins/OggUtil.hxx87
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.cxx535
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/OpusDomain.cxx24
-rw-r--r--src/decoder/plugins/OpusDomain.hxx27
-rw-r--r--src/decoder/plugins/OpusHead.cxx43
-rw-r--r--src/decoder/plugins/OpusHead.hxx30
-rw-r--r--src/decoder/plugins/OpusReader.hxx101
-rw-r--r--src/decoder/plugins/OpusTags.cxx102
-rw-r--r--src/decoder/plugins/OpusTags.hxx34
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.cxx111
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.hxx33
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.cxx439
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.cxx339
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/VorbisComments.cxx114
-rw-r--r--src/decoder/plugins/VorbisComments.hxx39
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.cxx389
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/VorbisDomain.cxx24
-rw-r--r--src/decoder/plugins/VorbisDomain.hxx29
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.cxx587
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.cxx164
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/XiphTags.cxx33
-rw-r--r--src/decoder/plugins/XiphTags.hxx28
-rw-r--r--src/encoder/EncoderAPI.hxx37
-rw-r--r--src/encoder/EncoderList.cxx68
-rw-r--r--src/encoder/EncoderList.hxx43
-rw-r--r--src/encoder/EncoderPlugin.hxx321
-rw-r--r--src/encoder/FlacEncoderPlugin.cxx342
-rw-r--r--src/encoder/FlacEncoderPlugin.hxx25
-rw-r--r--src/encoder/LameEncoderPlugin.cxx293
-rw-r--r--src/encoder/LameEncoderPlugin.hxx25
-rw-r--r--src/encoder/NullEncoderPlugin.cxx117
-rw-r--r--src/encoder/NullEncoderPlugin.hxx25
-rw-r--r--src/encoder/OggSerial.cxx43
-rw-r--r--src/encoder/OggSerial.hxx29
-rw-r--r--src/encoder/OggStream.hxx128
-rw-r--r--src/encoder/OpusEncoderPlugin.cxx420
-rw-r--r--src/encoder/OpusEncoderPlugin.hxx25
-rw-r--r--src/encoder/TwolameEncoderPlugin.cxx314
-rw-r--r--src/encoder/TwolameEncoderPlugin.hxx25
-rw-r--r--src/encoder/VorbisEncoderPlugin.cxx367
-rw-r--r--src/encoder/VorbisEncoderPlugin.hxx25
-rw-r--r--src/encoder/WaveEncoderPlugin.cxx274
-rw-r--r--src/encoder/WaveEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/FlacEncoderPlugin.cxx325
-rw-r--r--src/encoder/plugins/FlacEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/LameEncoderPlugin.cxx293
-rw-r--r--src/encoder/plugins/LameEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/NullEncoderPlugin.cxx105
-rw-r--r--src/encoder/plugins/NullEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/OggSerial.cxx43
-rw-r--r--src/encoder/plugins/OggSerial.hxx29
-rw-r--r--src/encoder/plugins/OggStream.hxx128
-rw-r--r--src/encoder/plugins/OpusEncoderPlugin.cxx419
-rw-r--r--src/encoder/plugins/OpusEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/ShineEncoderPlugin.cxx271
-rw-r--r--src/encoder/plugins/ShineEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/TwolameEncoderPlugin.cxx314
-rw-r--r--src/encoder/plugins/TwolameEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/VorbisEncoderPlugin.cxx364
-rw-r--r--src/encoder/plugins/VorbisEncoderPlugin.hxx25
-rw-r--r--src/encoder/plugins/WaveEncoderPlugin.cxx265
-rw-r--r--src/encoder/plugins/WaveEncoderPlugin.hxx25
-rw-r--r--src/event/BufferedSocket.cxx5
-rw-r--r--src/event/BufferedSocket.hxx9
-rw-r--r--src/event/Call.cxx23
-rw-r--r--src/event/Call.hxx2
-rw-r--r--src/event/DeferredMonitor.cxx61
-rw-r--r--src/event/DeferredMonitor.hxx58
-rw-r--r--src/event/FullyBufferedSocket.cxx11
-rw-r--r--src/event/FullyBufferedSocket.hxx3
-rw-r--r--src/event/IdleMonitor.cxx34
-rw-r--r--src/event/IdleMonitor.hxx38
-rw-r--r--src/event/Loop.cxx204
-rw-r--r--src/event/Loop.hxx164
-rw-r--r--src/event/MultiSocketMonitor.cxx141
-rw-r--r--src/event/MultiSocketMonitor.hxx139
-rw-r--r--src/event/PollGroup.hxx41
-rw-r--r--src/event/PollGroupEPoll.hxx91
-rw-r--r--src/event/PollGroupPoll.cxx89
-rw-r--r--src/event/PollGroupPoll.hxx63
-rw-r--r--src/event/PollGroupWinSelect.cxx161
-rw-r--r--src/event/PollGroupWinSelect.hxx111
-rw-r--r--src/event/PollResultGeneric.hxx57
-rw-r--r--src/event/ServerSocket.cxx51
-rw-r--r--src/event/ServerSocket.hxx5
-rw-r--r--src/event/SignalMonitor.cxx11
-rw-r--r--src/event/SignalMonitor.hxx2
-rw-r--r--src/event/SocketMonitor.cxx101
-rw-r--r--src/event/SocketMonitor.hxx89
-rw-r--r--src/event/TimeoutMonitor.cxx34
-rw-r--r--src/event/TimeoutMonitor.hxx33
-rw-r--r--src/event/WakeFD.hxx2
-rw-r--r--src/filter/AutoConvertFilterPlugin.cxx127
-rw-r--r--src/filter/AutoConvertFilterPlugin.hxx34
-rw-r--r--src/filter/ChainFilterPlugin.cxx182
-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/FilterConfig.cxx80
-rw-r--r--src/filter/FilterConfig.hxx43
-rw-r--r--src/filter/FilterInternal.hxx71
-rw-r--r--src/filter/FilterPlugin.cxx58
-rw-r--r--src/filter/FilterPlugin.hxx67
-rw-r--r--src/filter/FilterRegistry.cxx43
-rw-r--r--src/filter/FilterRegistry.hxx43
-rw-r--r--src/filter/NormalizeFilterPlugin.cxx84
-rw-r--r--src/filter/NullFilterPlugin.cxx61
-rw-r--r--src/filter/ReplayGainFilterPlugin.cxx236
-rw-r--r--src/filter/ReplayGainFilterPlugin.hxx52
-rw-r--r--src/filter/RouteFilterPlugin.cxx298
-rw-r--r--src/filter/VolumeFilterPlugin.cxx143
-rw-r--r--src/filter/VolumeFilterPlugin.hxx31
-rw-r--r--src/filter/plugins/AutoConvertFilterPlugin.cxx130
-rw-r--r--src/filter/plugins/AutoConvertFilterPlugin.hxx34
-rw-r--r--src/filter/plugins/ChainFilterPlugin.cxx180
-rw-r--r--src/filter/plugins/ChainFilterPlugin.hxx48
-rw-r--r--src/filter/plugins/ConvertFilterPlugin.cxx145
-rw-r--r--src/filter/plugins/ConvertFilterPlugin.hxx37
-rw-r--r--src/filter/plugins/NormalizeFilterPlugin.cxx81
-rw-r--r--src/filter/plugins/NullFilterPlugin.cxx60
-rw-r--r--src/filter/plugins/ReplayGainFilterPlugin.cxx208
-rw-r--r--src/filter/plugins/ReplayGainFilterPlugin.hxx52
-rw-r--r--src/filter/plugins/RouteFilterPlugin.cxx297
-rw-r--r--src/filter/plugins/VolumeFilterPlugin.cxx104
-rw-r--r--src/filter/plugins/VolumeFilterPlugin.hxx31
-rw-r--r--src/fs/AllocatedPath.cxx27
-rw-r--r--src/fs/AllocatedPath.hxx74
-rw-r--r--src/fs/Charset.cxx51
-rw-r--r--src/fs/Charset.hxx2
-rw-r--r--src/fs/CheckFile.cxx62
-rw-r--r--src/fs/CheckFile.hxx34
-rw-r--r--src/fs/Config.cxx16
-rw-r--r--src/fs/Config.hxx2
-rw-r--r--src/fs/DirectoryReader.hxx8
-rw-r--r--src/fs/Domain.cxx2
-rw-r--r--src/fs/Domain.hxx2
-rw-r--r--src/fs/FileSystem.cxx2
-rw-r--r--src/fs/FileSystem.hxx37
-rw-r--r--src/fs/Limits.hxx2
-rw-r--r--src/fs/Path.cxx24
-rw-r--r--src/fs/Path.hxx16
-rw-r--r--src/fs/StandardDirectory.cxx327
-rw-r--r--src/fs/StandardDirectory.hxx71
-rw-r--r--src/fs/Traits.cxx136
-rw-r--r--src/fs/Traits.hxx172
-rw-r--r--src/fs/io/AutoGunzipReader.cxx70
-rw-r--r--src/fs/io/AutoGunzipReader.hxx52
-rw-r--r--src/fs/io/BufferedOutputStream.cxx143
-rw-r--r--src/fs/io/BufferedOutputStream.hxx71
-rw-r--r--src/fs/io/BufferedReader.cxx82
-rw-r--r--src/fs/io/BufferedReader.hxx75
-rw-r--r--src/fs/io/FileOutputStream.cxx135
-rw-r--r--src/fs/io/FileOutputStream.hxx69
-rw-r--r--src/fs/io/FileReader.cxx98
-rw-r--r--src/fs/io/FileReader.hxx68
-rw-r--r--src/fs/io/GunzipReader.cxx101
-rw-r--r--src/fs/io/GunzipReader.hxx68
-rw-r--r--src/fs/io/GzipOutputStream.cxx105
-rw-r--r--src/fs/io/GzipOutputStream.hxx70
-rw-r--r--src/fs/io/OutputStream.hxx38
-rw-r--r--src/fs/io/PeekReader.cxx60
-rw-r--r--src/fs/io/PeekReader.hxx53
-rw-r--r--src/fs/io/Reader.hxx52
-rw-r--r--src/fs/io/StdioOutputStream.hxx46
-rw-r--r--src/fs/io/TextFile.cxx71
-rw-r--r--src/fs/io/TextFile.hxx73
-rw-r--r--src/gerror.h25
-rw-r--r--src/input/ArchiveInputPlugin.cxx99
-rw-r--r--src/input/ArchiveInputPlugin.hxx25
-rw-r--r--src/input/AsyncInputStream.cxx252
-rw-r--r--src/input/AsyncInputStream.hxx162
-rw-r--r--src/input/CdioParanoiaInputPlugin.cxx406
-rw-r--r--src/input/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/CurlInputPlugin.cxx1166
-rw-r--r--src/input/CurlInputPlugin.hxx25
-rw-r--r--src/input/DespotifyInputPlugin.cxx234
-rw-r--r--src/input/DespotifyInputPlugin.hxx25
-rw-r--r--src/input/Domain.cxx24
-rw-r--r--src/input/Domain.hxx27
-rw-r--r--src/input/FfmpegInputPlugin.cxx179
-rw-r--r--src/input/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/FileInputPlugin.cxx159
-rw-r--r--src/input/FileInputPlugin.hxx25
-rw-r--r--src/input/IcyInputStream.cxx99
-rw-r--r--src/input/IcyInputStream.hxx68
-rw-r--r--src/input/Init.cxx87
-rw-r--r--src/input/Init.hxx36
-rw-r--r--src/input/InputPlugin.hxx85
-rw-r--r--src/input/InputStream.cxx141
-rw-r--r--src/input/InputStream.hxx369
-rw-r--r--src/input/LocalOpen.cxx59
-rw-r--r--src/input/LocalOpen.hxx38
-rw-r--r--src/input/MmsInputPlugin.cxx130
-rw-r--r--src/input/MmsInputPlugin.hxx25
-rw-r--r--src/input/Offset.hxx32
-rw-r--r--src/input/Open.cxx78
-rw-r--r--src/input/ProxyInputStream.cxx100
-rw-r--r--src/input/ProxyInputStream.hxx64
-rw-r--r--src/input/Registry.cxx93
-rw-r--r--src/input/Registry.hxx43
-rw-r--r--src/input/RewindInputPlugin.cxx252
-rw-r--r--src/input/RewindInputPlugin.hxx37
-rw-r--r--src/input/TextInputStream.cxx84
-rw-r--r--src/input/TextInputStream.hxx52
-rw-r--r--src/input/ThreadInputStream.cxx173
-rw-r--r--src/input/ThreadInputStream.hxx144
-rw-r--r--src/input/plugins/AlsaInputPlugin.cxx385
-rw-r--r--src/input/plugins/AlsaInputPlugin.hxx28
-rw-r--r--src/input/plugins/ArchiveInputPlugin.cxx89
-rw-r--r--src/input/plugins/ArchiveInputPlugin.hxx34
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.cxx375
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/plugins/CurlInputPlugin.cxx876
-rw-r--r--src/input/plugins/CurlInputPlugin.hxx25
-rw-r--r--src/input/plugins/DespotifyInputPlugin.cxx227
-rw-r--r--src/input/plugins/DespotifyInputPlugin.hxx25
-rw-r--r--src/input/plugins/FfmpegInputPlugin.cxx154
-rw-r--r--src/input/plugins/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/plugins/FileInputPlugin.cxx138
-rw-r--r--src/input/plugins/FileInputPlugin.hxx36
-rw-r--r--src/input/plugins/MmsInputPlugin.cxx117
-rw-r--r--src/input/plugins/MmsInputPlugin.hxx25
-rw-r--r--src/input/plugins/NfsInputPlugin.cxx268
-rw-r--r--src/input/plugins/NfsInputPlugin.hxx25
-rw-r--r--src/input/plugins/RewindInputPlugin.cxx156
-rw-r--r--src/input/plugins/RewindInputPlugin.hxx37
-rw-r--r--src/input/plugins/SmbclientInputPlugin.cxx158
-rw-r--r--src/input/plugins/SmbclientInputPlugin.hxx25
-rw-r--r--src/java/Class.hxx83
-rw-r--r--src/java/Exception.hxx49
-rw-r--r--src/java/File.cxx67
-rw-r--r--src/java/File.hxx66
-rw-r--r--src/java/Global.cxx39
-rw-r--r--src/java/Global.hxx58
-rw-r--r--src/java/Object.hxx55
-rw-r--r--src/java/Ref.hxx181
-rw-r--r--src/java/String.cxx44
-rw-r--r--src/java/String.hxx75
-rw-r--r--src/lib/despotify/DespotifyUtils.cxx153
-rw-r--r--src/lib/despotify/DespotifyUtils.hxx71
-rw-r--r--src/lib/expat/ExpatParser.cxx92
-rw-r--r--src/lib/expat/ExpatParser.hxx129
-rw-r--r--src/lib/ffmpeg/Domain.cxx24
-rw-r--r--src/lib/ffmpeg/Domain.hxx27
-rw-r--r--src/lib/ffmpeg/Error.cxx43
-rw-r--r--src/lib/ffmpeg/Error.hxx31
-rw-r--r--src/lib/icu/Collate.cxx199
-rw-r--r--src/lib/icu/Collate.hxx44
-rw-r--r--src/lib/icu/Error.cxx24
-rw-r--r--src/lib/icu/Error.hxx29
-rw-r--r--src/lib/icu/Init.cxx48
-rw-r--r--src/lib/icu/Init.hxx42
-rw-r--r--src/lib/nfs/Base.cxx61
-rw-r--r--src/lib/nfs/Base.hxx46
-rw-r--r--src/lib/nfs/Blocking.cxx89
-rw-r--r--src/lib/nfs/Blocking.hxx90
-rw-r--r--src/lib/nfs/Callback.hxx33
-rw-r--r--src/lib/nfs/Cancellable.hxx168
-rw-r--r--src/lib/nfs/Connection.cxx662
-rw-r--r--src/lib/nfs/Connection.hxx239
-rw-r--r--src/lib/nfs/Domain.cxx24
-rw-r--r--src/lib/nfs/Domain.hxx27
-rw-r--r--src/lib/nfs/FileReader.cxx297
-rw-r--r--src/lib/nfs/FileReader.hxx101
-rw-r--r--src/lib/nfs/Glue.cxx59
-rw-r--r--src/lib/nfs/Glue.hxx38
-rw-r--r--src/lib/nfs/Lease.hxx48
-rw-r--r--src/lib/nfs/Manager.cxx107
-rw-r--r--src/lib/nfs/Manager.hxx116
-rw-r--r--src/lib/smbclient/Domain.cxx24
-rw-r--r--src/lib/smbclient/Domain.hxx27
-rw-r--r--src/lib/smbclient/Init.cxx55
-rw-r--r--src/lib/smbclient/Init.hxx33
-rw-r--r--src/lib/smbclient/Mutex.cxx24
-rw-r--r--src/lib/smbclient/Mutex.hxx31
-rw-r--r--src/lib/upnp/Action.hxx56
-rw-r--r--src/lib/upnp/Callback.hxx46
-rw-r--r--src/lib/upnp/ClientInit.cxx93
-rw-r--r--src/lib/upnp/ClientInit.hxx35
-rw-r--r--src/lib/upnp/ContentDirectoryService.cxx94
-rw-r--r--src/lib/upnp/ContentDirectoryService.hxx135
-rw-r--r--src/lib/upnp/Device.cxx134
-rw-r--r--src/lib/upnp/Device.hxx88
-rw-r--r--src/lib/upnp/Discovery.cxx340
-rw-r--r--src/lib/upnp/Discovery.hxx164
-rw-r--r--src/lib/upnp/Domain.cxx23
-rw-r--r--src/lib/upnp/Domain.hxx27
-rw-r--r--src/lib/upnp/Init.cxx73
-rw-r--r--src/lib/upnp/Init.hxx33
-rw-r--r--src/lib/upnp/Util.cxx138
-rw-r--r--src/lib/upnp/Util.hxx43
-rw-r--r--src/lib/upnp/WorkQueue.hxx206
-rw-r--r--src/lib/upnp/ixmlwrap.cxx44
-rw-r--r--src/lib/upnp/ixmlwrap.hxx35
-rw-r--r--src/lib/zlib/Domain.cxx24
-rw-r--r--src/lib/zlib/Domain.hxx27
-rw-r--r--src/ls.cxx20
-rw-r--r--src/ls.hxx5
-rw-r--r--src/mixer/AlsaMixerPlugin.cxx404
-rw-r--r--src/mixer/Listener.hxx34
-rw-r--r--src/mixer/MixerAll.cxx155
-rw-r--r--src/mixer/MixerControl.cxx147
-rw-r--r--src/mixer/MixerControl.hxx64
-rw-r--r--src/mixer/MixerInternal.hxx97
-rw-r--r--src/mixer/MixerList.hxx37
-rw-r--r--src/mixer/MixerPlugin.hxx60
-rw-r--r--src/mixer/MixerType.cxx39
-rw-r--r--src/mixer/MixerType.hxx47
-rw-r--r--src/mixer/OssMixerPlugin.cxx243
-rw-r--r--src/mixer/PulseMixerPlugin.cxx227
-rw-r--r--src/mixer/PulseMixerPlugin.hxx39
-rw-r--r--src/mixer/RoarMixerPlugin.cxx74
-rw-r--r--src/mixer/SoftwareMixerPlugin.cxx132
-rw-r--r--src/mixer/SoftwareMixerPlugin.hxx33
-rw-r--r--src/mixer/Volume.cxx124
-rw-r--r--src/mixer/Volume.hxx54
-rw-r--r--src/mixer/WinmmMixerPlugin.cxx114
-rw-r--r--src/mixer/plugins/AlsaMixerPlugin.cxx357
-rw-r--r--src/mixer/plugins/OssMixerPlugin.cxx203
-rw-r--r--src/mixer/plugins/PulseMixerPlugin.cxx233
-rw-r--r--src/mixer/plugins/PulseMixerPlugin.hxx36
-rw-r--r--src/mixer/plugins/RoarMixerPlugin.cxx72
-rw-r--r--src/mixer/plugins/SoftwareMixerPlugin.cxx144
-rw-r--r--src/mixer/plugins/SoftwareMixerPlugin.hxx33
-rw-r--r--src/mixer/plugins/WinmmMixerPlugin.cxx111
-rw-r--r--src/neighbor/Explorer.hxx71
-rw-r--r--src/neighbor/Glue.cxx111
-rw-r--r--src/neighbor/Glue.hxx76
-rw-r--r--src/neighbor/Info.hxx35
-rw-r--r--src/neighbor/Listener.hxx36
-rw-r--r--src/neighbor/NeighborPlugin.hxx40
-rw-r--r--src/neighbor/Registry.cxx46
-rw-r--r--src/neighbor/Registry.hxx37
-rw-r--r--src/neighbor/plugins/SmbclientNeighborPlugin.cxx288
-rw-r--r--src/neighbor/plugins/SmbclientNeighborPlugin.hxx27
-rw-r--r--src/neighbor/plugins/UpnpNeighborPlugin.cxx139
-rw-r--r--src/neighbor/plugins/UpnpNeighborPlugin.hxx27
-rw-r--r--src/notify.cxx2
-rw-r--r--src/notify.hxx4
-rw-r--r--src/open.h2
-rw-r--r--src/output/AlsaOutputPlugin.cxx869
-rw-r--r--src/output/AlsaOutputPlugin.hxx25
-rw-r--r--src/output/AoOutputPlugin.cxx286
-rw-r--r--src/output/AoOutputPlugin.hxx25
-rw-r--r--src/output/Domain.cxx23
-rw-r--r--src/output/Domain.hxx25
-rw-r--r--src/output/FifoOutputPlugin.cxx316
-rw-r--r--src/output/FifoOutputPlugin.hxx25
-rw-r--r--src/output/Finish.cxx50
-rw-r--r--src/output/HttpdClient.cxx467
-rw-r--r--src/output/HttpdClient.hxx190
-rw-r--r--src/output/HttpdInternal.hxx213
-rw-r--r--src/output/HttpdOutputPlugin.cxx565
-rw-r--r--src/output/HttpdOutputPlugin.hxx25
-rw-r--r--src/output/Init.cxx336
-rw-r--r--src/output/Internal.hxx441
-rw-r--r--src/output/JackOutputPlugin.cxx769
-rw-r--r--src/output/JackOutputPlugin.hxx25
-rw-r--r--src/output/MultipleOutputs.cxx482
-rw-r--r--src/output/MultipleOutputs.hxx276
-rw-r--r--src/output/NullOutputPlugin.cxx143
-rw-r--r--src/output/NullOutputPlugin.hxx25
-rw-r--r--src/output/OSXOutputPlugin.cxx433
-rw-r--r--src/output/OSXOutputPlugin.hxx25
-rw-r--r--src/output/OpenALOutputPlugin.cxx285
-rw-r--r--src/output/OpenALOutputPlugin.hxx25
-rw-r--r--src/output/OssOutputPlugin.cxx780
-rw-r--r--src/output/OssOutputPlugin.hxx25
-rw-r--r--src/output/OutputAPI.hxx33
-rw-r--r--src/output/OutputCommand.cxx106
-rw-r--r--src/output/OutputCommand.hxx53
-rw-r--r--src/output/OutputControl.cxx295
-rw-r--r--src/output/OutputControl.hxx25
-rw-r--r--src/output/OutputPlugin.cxx109
-rw-r--r--src/output/OutputPlugin.hxx204
-rw-r--r--src/output/OutputPrint.cxx43
-rw-r--r--src/output/OutputPrint.hxx34
-rw-r--r--src/output/OutputState.cxx88
-rw-r--r--src/output/OutputState.hxx46
-rw-r--r--src/output/OutputThread.cxx704
-rw-r--r--src/output/PipeOutputPlugin.cxx147
-rw-r--r--src/output/PipeOutputPlugin.hxx25
-rw-r--r--src/output/PulseOutputPlugin.cxx887
-rw-r--r--src/output/PulseOutputPlugin.hxx46
-rw-r--r--src/output/RecorderOutputPlugin.cxx262
-rw-r--r--src/output/RecorderOutputPlugin.hxx25
-rw-r--r--src/output/Registry.cxx104
-rw-r--r--src/output/Registry.hxx35
-rw-r--r--src/output/RoarOutputPlugin.cxx428
-rw-r--r--src/output/RoarOutputPlugin.hxx33
-rw-r--r--src/output/ShoutOutputPlugin.cxx544
-rw-r--r--src/output/ShoutOutputPlugin.hxx25
-rw-r--r--src/output/SolarisOutputPlugin.cxx201
-rw-r--r--src/output/SolarisOutputPlugin.hxx25
-rw-r--r--src/output/Timer.cxx67
-rw-r--r--src/output/Timer.hxx47
-rw-r--r--src/output/WinmmOutputPlugin.cxx353
-rw-r--r--src/output/WinmmOutputPlugin.hxx42
-rw-r--r--src/output/plugins/AlsaOutputPlugin.cxx895
-rw-r--r--src/output/plugins/AlsaOutputPlugin.hxx25
-rw-r--r--src/output/plugins/AoOutputPlugin.cxx282
-rw-r--r--src/output/plugins/AoOutputPlugin.hxx25
-rw-r--r--src/output/plugins/FifoOutputPlugin.cxx307
-rw-r--r--src/output/plugins/FifoOutputPlugin.hxx25
-rw-r--r--src/output/plugins/JackOutputPlugin.cxx762
-rw-r--r--src/output/plugins/JackOutputPlugin.hxx25
-rw-r--r--src/output/plugins/NullOutputPlugin.cxx138
-rw-r--r--src/output/plugins/NullOutputPlugin.hxx25
-rw-r--r--src/output/plugins/OSXOutputPlugin.cxx431
-rw-r--r--src/output/plugins/OSXOutputPlugin.hxx25
-rw-r--r--src/output/plugins/OpenALOutputPlugin.cxx282
-rw-r--r--src/output/plugins/OpenALOutputPlugin.hxx25
-rw-r--r--src/output/plugins/OssOutputPlugin.cxx779
-rw-r--r--src/output/plugins/OssOutputPlugin.hxx25
-rw-r--r--src/output/plugins/PipeOutputPlugin.cxx143
-rw-r--r--src/output/plugins/PipeOutputPlugin.hxx25
-rw-r--r--src/output/plugins/PulseOutputPlugin.cxx882
-rw-r--r--src/output/plugins/PulseOutputPlugin.hxx46
-rw-r--r--src/output/plugins/RecorderOutputPlugin.cxx258
-rw-r--r--src/output/plugins/RecorderOutputPlugin.hxx25
-rw-r--r--src/output/plugins/RoarOutputPlugin.cxx432
-rw-r--r--src/output/plugins/RoarOutputPlugin.hxx33
-rw-r--r--src/output/plugins/ShoutOutputPlugin.cxx537
-rw-r--r--src/output/plugins/ShoutOutputPlugin.hxx25
-rw-r--r--src/output/plugins/SolarisOutputPlugin.cxx198
-rw-r--r--src/output/plugins/SolarisOutputPlugin.hxx25
-rw-r--r--src/output/plugins/WinmmOutputPlugin.cxx352
-rw-r--r--src/output/plugins/WinmmOutputPlugin.hxx42
-rw-r--r--src/output/plugins/httpd/HttpdClient.cxx484
-rw-r--r--src/output/plugins/httpd/HttpdClient.hxx193
-rw-r--r--src/output/plugins/httpd/HttpdInternal.hxx268
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.cxx601
-rw-r--r--src/output/plugins/httpd/HttpdOutputPlugin.hxx25
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.cxx134
-rw-r--r--src/output/plugins/httpd/IcyMetaDataServer.hxx39
-rw-r--r--src/output/plugins/httpd/Page.cxx70
-rw-r--r--src/output/plugins/httpd/Page.hxx102
-rw-r--r--src/output/plugins/sles/AndroidSimpleBufferQueue.hxx67
-rw-r--r--src/output/plugins/sles/Engine.hxx68
-rw-r--r--src/output/plugins/sles/Object.hxx64
-rw-r--r--src/output/plugins/sles/Play.hxx52
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.cxx539
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.hxx25
-rw-r--r--src/pcm/ChannelsConverter.cxx97
-rw-r--r--src/pcm/ChannelsConverter.hxx83
-rw-r--r--src/pcm/ConfiguredResampler.cxx101
-rw-r--r--src/pcm/ConfiguredResampler.hxx38
-rw-r--r--src/pcm/Domain.cxx23
-rw-r--r--src/pcm/Domain.hxx27
-rw-r--r--src/pcm/FallbackResampler.cxx147
-rw-r--r--src/pcm/FallbackResampler.hxx46
-rw-r--r--src/pcm/FloatConvert.hxx64
-rw-r--r--src/pcm/FormatConverter.cxx103
-rw-r--r--src/pcm/FormatConverter.hxx84
-rw-r--r--src/pcm/GlueResampler.cxx85
-rw-r--r--src/pcm/GlueResampler.hxx63
-rw-r--r--src/pcm/LibsamplerateResampler.cxx163
-rw-r--r--src/pcm/LibsamplerateResampler.hxx56
-rw-r--r--src/pcm/Neon.hxx93
-rw-r--r--src/pcm/PcmBuffer.cxx3
-rw-r--r--src/pcm/PcmBuffer.hxx8
-rw-r--r--src/pcm/PcmChannels.cxx297
-rw-r--r--src/pcm/PcmChannels.hxx35
-rw-r--r--src/pcm/PcmConvert.cxx336
-rw-r--r--src/pcm/PcmConvert.hxx83
-rw-r--r--src/pcm/PcmDither.cxx76
-rw-r--r--src/pcm/PcmDither.hxx46
-rw-r--r--src/pcm/PcmDop.cxx96
-rw-r--r--src/pcm/PcmDop.hxx40
-rw-r--r--src/pcm/PcmDsd.cxx27
-rw-r--r--src/pcm/PcmDsd.hxx12
-rw-r--r--src/pcm/PcmDsdUsb.cxx96
-rw-r--r--src/pcm/PcmDsdUsb.hxx41
-rw-r--r--src/pcm/PcmExport.cxx77
-rw-r--r--src/pcm/PcmExport.hxx25
-rw-r--r--src/pcm/PcmFormat.cxx532
-rw-r--r--src/pcm/PcmFormat.hxx33
-rw-r--r--src/pcm/PcmMix.cxx119
-rw-r--r--src/pcm/PcmMix.hxx6
-rw-r--r--src/pcm/PcmPack.cxx2
-rw-r--r--src/pcm/PcmPack.hxx2
-rw-r--r--src/pcm/PcmPrng.hxx4
-rw-r--r--src/pcm/PcmResample.cxx173
-rw-r--r--src/pcm/PcmResample.hxx133
-rw-r--r--src/pcm/PcmResampleFallback.cxx106
-rw-r--r--src/pcm/PcmResampleInternal.hxx100
-rw-r--r--src/pcm/PcmResampleLibsamplerate.cxx310
-rw-r--r--src/pcm/PcmUtils.hxx48
-rw-r--r--src/pcm/PcmVolume.cxx188
-rw-r--r--src/pcm/PcmVolume.hxx81
-rw-r--r--src/pcm/Resampler.hxx74
-rw-r--r--src/pcm/ShiftConvert.hxx69
-rw-r--r--src/pcm/SoxrResampler.cxx164
-rw-r--r--src/pcm/SoxrResampler.hxx51
-rw-r--r--src/pcm/Traits.hxx153
-rw-r--r--src/pcm/Volume.cxx189
-rw-r--r--src/pcm/Volume.hxx130
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.c30
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.h30
-rw-r--r--src/pcm/dsd2pcm/dsd2pcm.hpp30
-rw-r--r--src/pcm/dsd2pcm/main.cpp30
-rw-r--r--src/pcm/dsd2pcm/noiseshape.c30
-rw-r--r--src/pcm/dsd2pcm/noiseshape.h30
-rw-r--r--src/pcm/dsd2pcm/noiseshape.hpp30
-rw-r--r--src/playlist/AsxPlaylistPlugin.cxx286
-rw-r--r--src/playlist/AsxPlaylistPlugin.hxx25
-rw-r--r--src/playlist/CloseSongEnumerator.cxx34
-rw-r--r--src/playlist/CloseSongEnumerator.hxx47
-rw-r--r--src/playlist/CuePlaylistPlugin.cxx91
-rw-r--r--src/playlist/CuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.cxx145
-rw-r--r--src/playlist/DespotifyPlaylistPlugin.hxx25
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.cxx184
-rw-r--r--src/playlist/EmbeddedCuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.cxx158
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/M3uPlaylistPlugin.cxx84
-rw-r--r--src/playlist/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/MemorySongEnumerator.cxx32
-rw-r--r--src/playlist/MemorySongEnumerator.hxx39
-rw-r--r--src/playlist/PlaylistAny.cxx40
-rw-r--r--src/playlist/PlaylistAny.hxx40
-rw-r--r--src/playlist/PlaylistMapper.cxx97
-rw-r--r--src/playlist/PlaylistMapper.hxx41
-rw-r--r--src/playlist/PlaylistPlugin.hxx109
-rw-r--r--src/playlist/PlaylistQueue.cxx99
-rw-r--r--src/playlist/PlaylistQueue.hxx62
-rw-r--r--src/playlist/PlaylistRegistry.cxx285
-rw-r--r--src/playlist/PlaylistRegistry.hxx74
-rw-r--r--src/playlist/PlaylistSong.cxx78
-rw-r--r--src/playlist/PlaylistSong.hxx36
-rw-r--r--src/playlist/PlaylistStream.cxx98
-rw-r--r--src/playlist/PlaylistStream.hxx46
-rw-r--r--src/playlist/PlsPlaylistPlugin.cxx182
-rw-r--r--src/playlist/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/Print.cxx75
-rw-r--r--src/playlist/Print.hxx36
-rw-r--r--src/playlist/RssPlaylistPlugin.cxx284
-rw-r--r--src/playlist/RssPlaylistPlugin.hxx25
-rw-r--r--src/playlist/SongEnumerator.hxx41
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.cxx421
-rw-r--r--src/playlist/SoundCloudPlaylistPlugin.hxx25
-rw-r--r--src/playlist/XspfPlaylistPlugin.cxx301
-rw-r--r--src/playlist/XspfPlaylistPlugin.hxx25
-rw-r--r--src/playlist/cue/CueParser.cxx316
-rw-r--r--src/playlist/cue/CueParser.hxx144
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.cxx186
-rw-r--r--src/playlist/plugins/AsxPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.cxx88
-rw-r--r--src/playlist/plugins/CuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/DespotifyPlaylistPlugin.cxx142
-rw-r--r--src/playlist/plugins/DespotifyPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx184
-rw-r--r--src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.cxx153
-rw-r--r--src/playlist/plugins/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.cxx83
-rw-r--r--src/playlist/plugins/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.cxx164
-rw-r--r--src/playlist/plugins/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx185
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.cxx401
-rw-r--r--src/playlist/plugins/SoundCloudPlaylistPlugin.hxx25
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.cxx234
-rw-r--r--src/playlist/plugins/XspfPlaylistPlugin.hxx25
-rw-r--r--src/poison.h2
-rw-r--r--src/protocol/Ack.cxx2
-rw-r--r--src/protocol/Ack.hxx2
-rw-r--r--src/protocol/ArgParser.cxx25
-rw-r--r--src/protocol/ArgParser.hxx10
-rw-r--r--src/protocol/Result.cxx4
-rw-r--r--src/protocol/Result.hxx2
-rw-r--r--src/queue/IdTable.hxx91
-rw-r--r--src/queue/Playlist.cxx341
-rw-r--r--src/queue/Playlist.hxx301
-rw-r--r--src/queue/PlaylistControl.cxx272
-rw-r--r--src/queue/PlaylistEdit.cxx491
-rw-r--r--src/queue/PlaylistState.cxx244
-rw-r--r--src/queue/PlaylistState.hxx53
-rw-r--r--src/queue/PlaylistTag.cxx93
-rw-r--r--src/queue/PlaylistUpdate.cxx73
-rw-r--r--src/queue/Queue.cxx482
-rw-r--r--src/queue/Queue.hxx378
-rw-r--r--src/queue/QueuePrint.cxx102
-rw-r--r--src/queue/QueuePrint.hxx54
-rw-r--r--src/queue/QueueSave.cxx126
-rw-r--r--src/queue/QueueSave.hxx43
-rw-r--r--src/sticker/SongSticker.cxx124
-rw-r--r--src/sticker/SongSticker.hxx86
-rw-r--r--src/sticker/StickerDatabase.cxx604
-rw-r--r--src/sticker/StickerDatabase.hxx161
-rw-r--r--src/sticker/StickerPrint.cxx44
-rw-r--r--src/sticker/StickerPrint.hxx38
-rw-r--r--src/storage/CompositeStorage.cxx362
-rw-r--r--src/storage/CompositeStorage.hxx166
-rw-r--r--src/storage/Configured.cxx82
-rw-r--r--src/storage/Configured.hxx45
-rw-r--r--src/storage/FileInfo.hxx62
-rw-r--r--src/storage/MemoryDirectoryReader.cxx48
-rw-r--r--src/storage/MemoryDirectoryReader.hxx67
-rw-r--r--src/storage/Registry.cxx71
-rw-r--r--src/storage/Registry.hxx45
-rw-r--r--src/storage/StorageInterface.cxx37
-rw-r--r--src/storage/StorageInterface.hxx81
-rw-r--r--src/storage/StoragePlugin.hxx36
-rw-r--r--src/storage/plugins/LocalStorage.cxx217
-rw-r--r--src/storage/plugins/LocalStorage.hxx36
-rw-r--r--src/storage/plugins/NfsStorage.cxx423
-rw-r--r--src/storage/plugins/NfsStorage.hxx29
-rw-r--r--src/storage/plugins/SmbclientStorage.cxx212
-rw-r--r--src/storage/plugins/SmbclientStorage.hxx29
-rw-r--r--src/system/Clock.cxx50
-rw-r--r--src/system/Clock.hxx20
-rw-r--r--src/system/EPollFD.cxx17
-rw-r--r--src/system/EPollFD.hxx3
-rw-r--r--src/system/EventFD.cxx2
-rw-r--r--src/system/EventFD.hxx2
-rw-r--r--src/system/EventPipe.cxx2
-rw-r--r--src/system/EventPipe.hxx2
-rw-r--r--src/system/FatalError.cxx13
-rw-r--r--src/system/FatalError.hxx7
-rw-r--r--src/system/PeriodClock.hxx149
-rw-r--r--src/system/Resolver.cxx65
-rw-r--r--src/system/Resolver.hxx20
-rw-r--r--src/system/SignalFD.cxx3
-rw-r--r--src/system/SignalFD.hxx2
-rw-r--r--src/system/SocketError.cxx6
-rw-r--r--src/system/SocketError.hxx8
-rw-r--r--src/system/SocketUtil.cxx4
-rw-r--r--src/system/SocketUtil.hxx2
-rw-r--r--src/system/fd_util.c8
-rw-r--r--src/system/fd_util.h9
-rw-r--r--src/tag/Aiff.cxx4
-rw-r--r--src/tag/Aiff.hxx2
-rw-r--r--src/tag/ApeLoader.cxx10
-rw-r--r--src/tag/ApeLoader.hxx2
-rw-r--r--src/tag/ApeReplayGain.cxx19
-rw-r--r--src/tag/ApeReplayGain.hxx2
-rw-r--r--src/tag/ApeTag.cxx2
-rw-r--r--src/tag/ApeTag.hxx4
-rw-r--r--src/tag/MixRamp.cxx79
-rw-r--r--src/tag/MixRamp.hxx33
-rw-r--r--src/tag/ReplayGain.cxx86
-rw-r--r--src/tag/ReplayGain.hxx33
-rw-r--r--src/tag/Riff.cxx3
-rw-r--r--src/tag/Riff.hxx2
-rw-r--r--src/tag/Set.cxx117
-rw-r--r--src/tag/Set.hxx73
-rw-r--r--src/tag/Tag.cxx121
-rw-r--r--src/tag/Tag.hxx114
-rw-r--r--src/tag/TagBuilder.cxx173
-rw-r--r--src/tag/TagBuilder.hxx70
-rw-r--r--src/tag/TagConfig.cxx18
-rw-r--r--src/tag/TagConfig.hxx4
-rw-r--r--src/tag/TagHandler.cxx6
-rw-r--r--src/tag/TagHandler.hxx11
-rw-r--r--src/tag/TagId3.cxx182
-rw-r--r--src/tag/TagId3.hxx12
-rw-r--r--src/tag/TagItem.hxx2
-rw-r--r--src/tag/TagNames.c4
-rw-r--r--src/tag/TagPool.cxx93
-rw-r--r--src/tag/TagPool.hxx2
-rw-r--r--src/tag/TagRva2.cxx6
-rw-r--r--src/tag/TagRva2.hxx4
-rw-r--r--src/tag/TagSettings.c2
-rw-r--r--src/tag/TagSettings.h2
-rw-r--r--src/tag/TagString.cxx97
-rw-r--r--src/tag/TagString.hxx8
-rw-r--r--src/tag/TagTable.cxx12
-rw-r--r--src/tag/TagTable.hxx11
-rw-r--r--src/tag/TagType.h4
-rw-r--r--src/tag/VorbisComment.cxx41
-rw-r--r--src/tag/VorbisComment.hxx34
-rw-r--r--src/thread/Cond.hxx40
-rw-r--r--src/thread/CriticalSection.hxx4
-rw-r--r--src/thread/GLibCond.hxx88
-rw-r--r--src/thread/GLibMutex.hxx90
-rw-r--r--src/thread/Id.hxx2
-rw-r--r--src/thread/Mutex.hxx40
-rw-r--r--src/thread/Name.hxx59
-rw-r--r--src/thread/PosixCond.hxx6
-rw-r--r--src/thread/PosixMutex.hxx6
-rw-r--r--src/thread/Slack.hxx54
-rw-r--r--src/thread/Thread.cxx11
-rw-r--r--src/thread/Thread.hxx4
-rw-r--r--src/thread/Util.hxx101
-rw-r--r--src/thread/WindowsCond.hxx4
-rw-r--r--src/unix/Daemon.cxx268
-rw-r--r--src/unix/Daemon.hxx99
-rw-r--r--src/unix/PidFile.hxx85
-rw-r--r--src/unix/SignalHandlers.cxx82
-rw-r--r--src/unix/SignalHandlers.hxx31
-rw-r--r--src/util/Alloc.cxx76
-rw-r--r--src/util/Alloc.hxx67
-rw-r--r--src/util/ByteReverse.cxx2
-rw-r--r--src/util/ByteReverse.hxx2
-rw-r--r--src/util/Cast.hxx109
-rw-r--r--src/util/CharUtil.hxx60
-rw-r--r--src/util/CircularBuffer.hxx186
-rw-r--r--src/util/Clamp.hxx49
-rw-r--r--src/util/ConstBuffer.hxx251
-rw-r--r--src/util/Domain.hxx40
-rw-r--r--src/util/DynamicFifoBuffer.hxx111
-rw-r--r--src/util/Error.cxx78
-rw-r--r--src/util/Error.hxx54
-rw-r--r--src/util/FifoBuffer.hxx132
-rw-r--r--src/util/ForeignFifoBuffer.hxx250
-rw-r--r--src/util/FormatString.cxx7
-rw-r--r--src/util/FormatString.hxx2
-rw-r--r--src/util/HugeAllocator.cxx36
-rw-r--r--src/util/HugeAllocator.hxx65
-rw-r--r--src/util/LazyRandomEngine.cxx2
-rw-r--r--src/util/LazyRandomEngine.hxx2
-rw-r--r--src/util/OptionDef.hxx63
-rw-r--r--src/util/OptionParser.cxx59
-rw-r--r--src/util/OptionParser.hxx88
-rw-r--r--src/util/PeakBuffer.cxx74
-rw-r--r--src/util/PeakBuffer.hxx13
-rw-r--r--src/util/RefCount.hxx2
-rw-r--r--src/util/SliceBuffer.hxx2
-rw-r--r--src/util/SplitString.cxx37
-rw-r--r--src/util/SplitString.hxx71
-rw-r--r--src/util/StaticFifoBuffer.hxx134
-rw-r--r--src/util/StringUtil.cxx80
-rw-r--r--src/util/StringUtil.hxx76
-rw-r--r--src/util/TextFile.hxx52
-rw-r--r--src/util/Tokenizer.cxx53
-rw-r--r--src/util/Tokenizer.hxx40
-rw-r--r--src/util/UTF8.cxx345
-rw-r--r--src/util/UTF8.hxx90
-rw-r--r--src/util/UriUtil.cxx42
-rw-r--r--src/util/UriUtil.hxx17
-rw-r--r--src/util/VarSize.hxx83
-rw-r--r--src/util/WritableBuffer.hxx201
-rw-r--r--src/util/bit_reverse.c2
-rw-r--r--src/util/bit_reverse.h2
-rw-r--r--src/util/fifo_buffer.c218
-rw-r--r--src/util/fifo_buffer.h164
-rw-r--r--src/util/growing_fifo.c90
-rw-r--r--src/util/growing_fifo.h73
-rw-r--r--src/util/list.h607
-rw-r--r--src/util/list_sort.c162
-rw-r--r--src/util/list_sort.h33
-rw-r--r--src/win32/Win32Main.cxx2
-rw-r--r--src/zeroconf/AvahiPoll.cxx146
-rw-r--r--src/zeroconf/AvahiPoll.hxx48
-rw-r--r--src/zeroconf/ZeroconfAvahi.cxx282
-rw-r--r--src/zeroconf/ZeroconfAvahi.hxx31
-rw-r--r--src/zeroconf/ZeroconfBonjour.cxx108
-rw-r--r--src/zeroconf/ZeroconfBonjour.hxx31
-rw-r--r--src/zeroconf/ZeroconfGlue.cxx83
-rw-r--r--src/zeroconf/ZeroconfGlue.hxx47
-rw-r--r--src/zeroconf/ZeroconfInternal.hxx26
-rw-r--r--systemd/mpd.service.in22
-rw-r--r--systemd/mpd.socket9
-rw-r--r--test/.gitignore1
-rw-r--r--test/DumpDatabase.cxx75
-rw-r--r--test/FakeDecoderAPI.cxx59
-rw-r--r--test/FakeDecoderAPI.hxx37
-rw-r--r--test/FakeReplayGainConfig.cxx2
-rw-r--r--test/FakeSong.cxx33
-rw-r--r--test/ScopeIOThread.hxx36
-rw-r--r--test/ShutdownHandler.cxx2
-rw-r--r--test/ShutdownHandler.hxx4
-rw-r--r--test/TestCircularBuffer.hxx163
-rw-r--r--test/dump_playlist.cxx109
-rw-r--r--test/dump_rva2.cxx26
-rw-r--r--test/dump_text_file.cxx71
-rw-r--r--test/read_conf.cxx38
-rw-r--r--test/read_mixer.cxx116
-rw-r--r--test/read_tags.cxx73
-rw-r--r--test/run_avahi.cxx6
-rw-r--r--test/run_convert.cxx58
-rw-r--r--test/run_decoder.cxx222
-rw-r--r--test/run_encoder.cxx50
-rw-r--r--test/run_filter.cxx93
-rw-r--r--test/run_gunzip.cxx76
-rw-r--r--test/run_gzip.cxx79
-rw-r--r--test/run_inotify.cxx19
-rw-r--r--test/run_input.cxx82
-rw-r--r--test/run_neighbor_explorer.cxx85
-rw-r--r--test/run_normalize.cxx9
-rw-r--r--test/run_output.cxx117
-rw-r--r--test/run_resolver.cxx19
-rw-r--r--test/run_storage.cxx133
-rw-r--r--test/software_volume.cxx37
-rw-r--r--test/stdbin.h2
-rw-r--r--test/test_archive.cxx16
-rw-r--r--test/test_byte_reverse.cxx2
-rw-r--r--test/test_icy_parser.cxx4
-rw-r--r--test/test_pcm_all.hxx27
-rw-r--r--test/test_pcm_channels.cxx39
-rw-r--r--test/test_pcm_dither.cxx8
-rw-r--r--test/test_pcm_export.cxx129
-rw-r--r--test/test_pcm_format.cxx70
-rw-r--r--test/test_pcm_main.cxx10
-rw-r--r--test/test_pcm_mix.cxx20
-rw-r--r--test/test_pcm_pack.cxx6
-rw-r--r--test/test_pcm_util.hxx12
-rw-r--r--test/test_pcm_volume.cxx190
-rw-r--r--test/test_queue_priority.cxx43
-rw-r--r--test/test_rewind.cxx154
-rw-r--r--test/test_translate_song.cxx318
-rw-r--r--test/test_util.cxx4
-rw-r--r--test/test_vorbis_encoder.cxx18
-rw-r--r--test/visit_archive.cxx41
-rw-r--r--valgrind.suppressions125
1519 files changed, 97046 insertions, 69249 deletions
diff --git a/.gitignore b/.gitignore
index 790eac57b..ba6d3b30e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,75 +6,78 @@
*.lo
*.o
*.exe
-.deps
-.dirstamp
-Makefile
-Makefile.in
-aclocal.m4
-autom4te.cache
-compile
-config.guess
-config.h
-config.h.in
-config.log
-config.mk
-config.status
-config.sub
-config_detected.h
-config_detected.mk
-configure
-configure.lineno
-depcomp
-depmode
-install-sh
-libtool
-ltmain.sh
-missing
-mkinstalldirs
-/test-driver
-mpd
-mpd.service
-stamp-h1
-tags
+
*~
+
.#*
.stgit*
-src/dsd2pcm/dsd2pcm
-src/win32/mpd_win32_rc.rc
-doc/doxygen.conf
-doc/protocol.html
-doc/protocol
-doc/user
-doc/developer
-doc/sticker
-doc/api
-test/software_volume
-test/run_convert
-test/run_decoder
-test/read_tags
-test/run_filter
-test/run_encoder
-test/run_output
-test/read_conf
-test/run_input
-test/read_mixer
-test/dump_playlist
-test/run_normalize
-test/tmp
-test/run_inotify
-test/test_queue_priority
-test/test_protocol
-test/run_ntp_server
-test/run_resolver
-test/run_tcp_connect
-test/test_pcm
-test/dump_rva2
-test/dump_text_file
-test/test_util
-test/test_byte_reverse
-test/test_mixramp
-test/test_vorbis_encoder
-test/DumpDatabase
+.deps
+.dirstamp
+
+tags
+
+/Makefile
+/Makefile.in
+/aclocal.m4
+/autom4te.cache
+/config.h
+/config.h.in
+/config.log
+/config.mk
+/config.status
+/config_detected.h
+/config_detected.mk
+/configure
+/configure.lineno
+/depmode
+/libtool
+/ltmain.sh
+/mkinstalldirs
+/build
+/src/mpd
+/systemd/mpd.service
+/stamp-h1
+
+/src/dsd2pcm/dsd2pcm
+/src/win32/mpd_win32_rc.rc
+
+/doc/doxygen.conf
+/doc/protocol.html
+/doc/protocol
+/doc/user
+/doc/developer
+/doc/sticker
+/doc/api
+
+/test/software_volume
+/test/run_convert
+/test/run_decoder
+/test/read_tags
+/test/run_filter
+/test/run_encoder
+/test/run_output
+/test/read_conf
+/test/run_input
+/test/read_mixer
+/test/dump_playlist
+/test/run_normalize
+/test/tmp
+/test/run_inotify
+/test/test_queue_priority
+/test/test_protocol
+/test/run_ntp_server
+/test/run_resolver
+/test/run_tcp_connect
+/test/test_pcm
+/test/dump_rva2
+/test/dump_text_file
+/test/test_util
+/test/test_byte_reverse
+/test/test_mixramp
+/test/test_vorbis_encoder
+/test/DumpDatabase
+
+/lib/
/*.tar.gz
/*.tar.bz2
diff --git a/AUTHORS b/AUTHORS
index 234057ef1..2cfa76602 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,5 +1,5 @@
Music Player Daemon - http://www.musicpd.org
-Copyright (C) 2003-2013 The Music Player Daemon Project
+Copyright (C) 2003-2014 The Music Player Daemon Project
The following people have contributed code to MPD:
diff --git a/INSTALL b/INSTALL
index 9d91ac762..29a23d361 100644
--- a/INSTALL
+++ b/INSTALL
@@ -16,6 +16,8 @@ gcc 4.6 or later - http://gcc.gnu.org/
clang 3.2 or later - http://clang.llvm.org/
Any other C++11 compliant compiler should also work.
+Boost 1.46 - http://www.boost.org/
+
GLib 2.28 - http://www.gtk.org/
General-purpose utility library.
diff --git a/Makefile.am b/Makefile.am
index a96d69369..e8f459454 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,36 +1,46 @@
ACLOCAL_AMFLAGS = -I m4
AUTOMAKE_OPTIONS = foreign 1.11 dist-xz subdir-objects
-AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS)
+AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS) $(BOOST_CPPFLAGS)
AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
+if ANDROID
+else
bin_PROGRAMS = src/mpd
+endif
noinst_LIBRARIES = \
+ libmpd.a \
libutil.a \
libthread.a \
libsystem.a \
libevent.a \
+ libicu.a \
libpcm.a \
libconf.a \
libtag.a \
libinput.a \
libfs.a \
- libdb_plugins.a \
libplaylist_plugins.a \
- libdecoder_plugins.a \
+ libdecoder.a \
libfilter_plugins.a \
libmixer_plugins.a \
liboutput_plugins.a
-src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
+libmpd_a_DEPENDENCIES =
+
+libmpd_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(LIBMPDCLIENT_CFLAGS) \
$(AVAHI_CFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS)
+
src_mpd_LDADD = \
+ libmpd.a \
+ $(NEIGHBOR_LIBS) \
$(DB_LIBS) \
+ $(STORAGE_LIBS) \
$(PLAYLIST_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
@@ -47,29 +57,22 @@ src_mpd_LDADD = \
libevent.a \
libthread.a \
libsystem.a \
+ $(ICU_LDADD) \
libutil.a \
- libfs.a \
+ $(FS_LIBS) \
$(SYSTEMD_DAEMON_LIBS) \
$(GLIB_LIBS)
-mpd_headers = \
- src/check.h \
- src/gerror.h \
- src/TextInputStream.hxx \
- src/AudioCompress/config.h \
- src/AudioCompress/compress.h \
- src/open.h \
- src/Playlist.hxx \
- src/poison.h \
- src/TimePrint.cxx src/TimePrint.hxx \
- src/Timer.hxx
-
src_mpd_SOURCES = \
- $(mpd_headers) \
- $(DECODER_SRC) \
+ src/Main.cxx src/Main.hxx
+
+libmpd_a_SOURCES = \
$(OUTPUT_API_SRC) \
$(MIXER_API_SRC) \
+ src/check.h \
src/Compiler.h \
+ src/open.h \
+ src/poison.h \
src/notify.cxx src/notify.hxx \
src/AudioConfig.cxx src/AudioConfig.hxx \
src/CheckAudioFormat.cxx src/CheckAudioFormat.hxx \
@@ -82,130 +85,241 @@ src_mpd_SOURCES = \
src/command/CommandError.cxx src/command/CommandError.hxx \
src/command/AllCommands.cxx src/command/AllCommands.hxx \
src/command/QueueCommands.cxx src/command/QueueCommands.hxx \
+ src/command/TagCommands.cxx src/command/TagCommands.hxx \
src/command/PlayerCommands.cxx src/command/PlayerCommands.hxx \
src/command/PlaylistCommands.cxx src/command/PlaylistCommands.hxx \
- src/command/DatabaseCommands.cxx src/command/DatabaseCommands.hxx \
src/command/FileCommands.cxx src/command/FileCommands.hxx \
src/command/OutputCommands.cxx src/command/OutputCommands.hxx \
src/command/MessageCommands.cxx src/command/MessageCommands.hxx \
src/command/OtherCommands.cxx src/command/OtherCommands.hxx \
src/command/CommandListBuilder.cxx src/command/CommandListBuilder.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.cxx 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/DatabaseError.cxx src/DatabaseError.hxx \
- 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/FilterConfig.cxx src/FilterConfig.hxx \
- src/FilterPlugin.cxx src/FilterPlugin.hxx \
- src/FilterInternal.hxx \
- src/FilterRegistry.cxx src/FilterRegistry.hxx \
- src/UpdateDomain.cxx src/UpdateDomain.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/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/ClientFile.cxx src/ClientFile.hxx \
+ src/decoder/DecoderError.cxx src/decoder/DecoderError.hxx \
+ src/decoder/DecoderThread.cxx src/decoder/DecoderThread.hxx \
+ src/decoder/DecoderCommand.hxx \
+ src/decoder/DecoderControl.cxx src/decoder/DecoderControl.hxx \
+ src/decoder/DecoderAPI.cxx src/decoder/DecoderAPI.hxx \
+ src/decoder/DecoderPlugin.hxx \
+ src/decoder/DecoderInternal.cxx src/decoder/DecoderInternal.hxx \
+ src/decoder/DecoderPrint.cxx src/decoder/DecoderPrint.hxx \
+ src/filter/FilterConfig.cxx src/filter/FilterConfig.hxx \
+ src/filter/FilterPlugin.cxx src/filter/FilterPlugin.hxx \
+ src/filter/FilterInternal.hxx \
+ src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \
+ src/client/Client.cxx src/client/Client.hxx \
+ src/client/ClientInternal.hxx \
+ src/client/ClientEvent.cxx \
+ src/client/ClientExpire.cxx \
+ src/client/ClientGlobal.cxx \
+ src/client/ClientIdle.cxx \
+ src/client/ClientList.cxx src/client/ClientList.hxx \
+ src/client/ClientNew.cxx \
+ src/client/ClientProcess.cxx \
+ src/client/ClientRead.cxx \
+ src/client/ClientWrite.cxx \
+ src/client/ClientMessage.cxx src/client/ClientMessage.hxx \
+ src/client/ClientSubscribe.cxx \
+ src/client/ClientFile.cxx \
src/Listen.cxx src/Listen.hxx \
src/LogInit.cxx src/LogInit.hxx \
+ src/LogBackend.cxx src/LogBackend.hxx \
src/Log.cxx src/Log.hxx src/LogV.hxx \
+ src/LogLevel.hxx \
src/ls.cxx src/ls.hxx \
src/IOThread.cxx src/IOThread.hxx \
- src/Main.cxx src/Main.hxx \
src/Instance.cxx src/Instance.hxx \
src/win32/Win32Main.cxx \
src/GlobalEvents.cxx src/GlobalEvents.hxx \
- src/Daemon.cxx src/Daemon.hxx \
- src/AudioCompress/compress.c \
src/MixRampInfo.hxx \
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.cxx 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/PlayerListener.hxx \
src/PlaylistError.cxx src/PlaylistError.hxx \
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/PlaylistDatabase.hxx \
- src/PlaylistUpdate.cxx \
+ src/playlist/PlaylistStream.cxx src/playlist/PlaylistStream.hxx \
+ src/playlist/PlaylistMapper.cxx src/playlist/PlaylistMapper.hxx \
+ src/playlist/PlaylistAny.cxx src/playlist/PlaylistAny.hxx \
+ src/playlist/PlaylistSong.cxx src/playlist/PlaylistSong.hxx \
+ src/playlist/PlaylistQueue.cxx src/playlist/PlaylistQueue.hxx \
+ src/playlist/Print.cxx src/playlist/Print.hxx \
src/BulkEdit.hxx \
- src/IdTable.hxx \
- src/Queue.cxx src/Queue.hxx \
- src/QueuePrint.cxx src/QueuePrint.hxx \
- src/QueueSave.cxx src/QueueSave.hxx \
+ src/db/PlaylistVector.cxx src/db/PlaylistVector.hxx \
+ src/db/PlaylistInfo.hxx \
+ src/queue/IdTable.hxx \
+ src/queue/Queue.cxx src/queue/Queue.hxx \
+ src/queue/QueuePrint.cxx src/queue/QueuePrint.hxx \
+ src/queue/QueueSave.cxx src/queue/QueueSave.hxx \
+ src/queue/Playlist.cxx src/queue/Playlist.hxx \
+ src/queue/PlaylistControl.cxx \
+ src/queue/PlaylistEdit.cxx \
+ src/queue/PlaylistTag.cxx \
+ src/queue/PlaylistState.cxx src/queue/PlaylistState.hxx \
src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \
src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \
- src/SignalHandlers.cxx src/SignalHandlers.hxx \
- src/Song.cxx src/Song.hxx \
+ src/DetachedSong.cxx src/DetachedSong.hxx \
src/SongUpdate.cxx \
+ src/SongLoader.cxx src/SongLoader.hxx \
src/SongPrint.cxx src/SongPrint.hxx \
src/SongSave.cxx src/SongSave.hxx \
- src/SongSort.cxx src/SongSort.hxx \
src/StateFile.cxx src/StateFile.hxx \
src/Stats.cxx src/Stats.hxx \
src/TagPrint.cxx src/TagPrint.hxx \
src/TagSave.cxx src/TagSave.hxx \
src/TagFile.cxx src/TagFile.hxx \
- src/TextFile.cxx src/TextFile.hxx \
- src/TextInputStream.cxx \
- src/Volume.cxx src/Volume.hxx \
+ src/TagStream.cxx src/TagStream.hxx \
+ src/TimePrint.cxx src/TimePrint.hxx \
+ src/mixer/Volume.cxx src/mixer/Volume.hxx \
+ src/Chrono.hxx \
src/SongFilter.cxx src/SongFilter.hxx \
- src/SongPointer.hxx \
- src/PlaylistFile.cxx src/PlaylistFile.hxx \
- src/Timer.cxx
+ src/PlaylistFile.cxx src/PlaylistFile.hxx
+
+if ANDROID
+else
+libmpd_a_SOURCES += \
+ src/unix/SignalHandlers.cxx src/unix/SignalHandlers.hxx \
+ src/unix/Daemon.cxx src/unix/Daemon.hxx \
+ src/unix/PidFile.hxx \
+ src/CommandLine.cxx src/CommandLine.hxx
+endif
+
+if ENABLE_DATABASE
+libmpd_a_SOURCES += \
+ src/queue/PlaylistUpdate.cxx \
+ src/command/StorageCommands.cxx src/command/StorageCommands.hxx \
+ src/command/DatabaseCommands.cxx src/command/DatabaseCommands.hxx \
+ src/db/Count.cxx src/db/Count.hxx \
+ src/db/LightSong.cxx src/db/LightSong.hxx \
+ src/db/LightDirectory.hxx \
+ src/db/update/UpdateDomain.cxx src/db/update/UpdateDomain.hxx \
+ src/db/update/Service.cxx src/db/update/Service.hxx \
+ src/db/update/Queue.cxx src/db/update/Queue.hxx \
+ src/db/update/UpdateIO.cxx src/db/update/UpdateIO.hxx \
+ src/db/update/Editor.cxx src/db/update/Editor.hxx \
+ src/db/update/Walk.cxx src/db/update/Walk.hxx \
+ src/db/update/UpdateSong.cxx \
+ src/db/update/Container.cxx \
+ src/db/update/Remove.cxx src/db/update/Remove.hxx \
+ src/db/update/ExcludeList.cxx src/db/update/ExcludeList.hxx \
+ src/db/Uri.hxx \
+ src/db/DatabaseGlue.cxx src/db/DatabaseGlue.hxx \
+ src/db/Configured.cxx src/db/Configured.hxx \
+ src/db/DatabaseSong.cxx src/db/DatabaseSong.hxx \
+ src/db/DatabasePrint.cxx src/db/DatabasePrint.hxx \
+ src/db/DatabaseQueue.cxx src/db/DatabaseQueue.hxx \
+ src/db/DatabasePlaylist.cxx src/db/DatabasePlaylist.hxx \
+ src/db/DatabaseError.cxx src/db/DatabaseError.hxx \
+ src/db/DatabaseLock.cxx src/db/DatabaseLock.hxx \
+ src/db/DatabasePlugin.hxx \
+ src/db/Interface.hxx \
+ src/db/Stats.hxx \
+ src/db/DatabaseListener.hxx \
+ src/db/Visitor.hxx \
+ src/db/Selection.cxx src/db/Selection.hxx
+endif
+
+UPNP_SOURCES = \
+ src/lib/upnp/Init.cxx src/lib/upnp/Init.hxx \
+ src/lib/upnp/ClientInit.cxx src/lib/upnp/ClientInit.hxx \
+ src/lib/upnp/Device.cxx src/lib/upnp/Device.hxx \
+ src/lib/upnp/ContentDirectoryService.cxx src/lib/upnp/ContentDirectoryService.hxx \
+ src/lib/upnp/Discovery.cxx src/lib/upnp/Discovery.hxx \
+ src/lib/upnp/Domain.cxx src/lib/upnp/Domain.hxx \
+ src/lib/upnp/ixmlwrap.cxx src/lib/upnp/ixmlwrap.hxx \
+ src/lib/upnp/Callback.hxx \
+ src/lib/upnp/Util.cxx src/lib/upnp/Util.hxx \
+ src/lib/upnp/WorkQueue.hxx \
+ src/lib/upnp/Action.hxx
+
+#
+# Android native library
+#
+
+if ANDROID
+
+noinst_LIBRARIES += libjava.a
+libjava_a_SOURCES = \
+ src/java/Class.hxx \
+ src/java/Exception.hxx \
+ src/java/Global.cxx src/java/Global.hxx \
+ src/java/Object.hxx \
+ src/java/Ref.hxx \
+ src/java/File.cxx src/java/File.hxx \
+ src/java/String.cxx src/java/String.hxx
+
+noinst_LIBRARIES += libandroid.a
+libandroid_a_SOURCES = \
+ src/android/Context.cxx src/android/Context.hxx \
+ src/android/Environment.cxx src/android/Environment.hxx
+libandroid_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
+
+noinst_LIBRARIES += libmain.a
+libmain_a_SOURCES = \
+ src/Main.cxx src/Main.hxx
+libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
+
+src_mpd_LDADD += libandroid.a libjava.a
+
+all-local: android/build/bin/Main-debug.apk
+clean-local:
+ rm -rf android/build
+
+libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
+ $(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
+
+android/build/build.xml: android/AndroidManifest.xml
+ rm -rf android/build
+ mkdir -p android/build/include android/build/res android/build/src/org
+ ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
+ ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
+ ln -s $(abs_srcdir)/android/res/values android/build/res
+ $(ANDROID_SDK)/tools/android update project --path android/build --target android-17
+
+android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml
+ cd android/build && ant compile-jni-classes
+
+android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
+ javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
+
+libmpd_a_DEPENDENCIES += android/build/include/org_musicpd_Bridge.h
+
+android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
+ mkdir -p $(@D)
+ rm -f $@
+ $(STRIP) -o $@ $<
+
+android/build/res/drawable/icon.png: mpd.svg
+ mkdir -p $(@D)
+ rsvg-convert --width=48 --height=48 $< -o $@
+
+APK_DEPS = android/build/res/drawable/icon.png \
+ android/build/libs/armeabi-v7a/libmpd.so \
+ $(wildcard $(srcdir)/android/src/*.java) \
+ android/build/build.xml
+
+android/build/bin/Main-debug.apk: $(APK_DEPS)
+ cd android/build && ant nodeps debug
+
+android/build/bin/Main-release-unsigned.apk: $(APK_DEPS)
+ cd android/build && ant nodeps release
+
+android/build/bin/Main-release-unaligned.apk: android/build/bin/Main-release-unsigned.apk
+ jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
+
+ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
+
+android/build/bin/Main.apk: android/build/bin/Main-release-unaligned.apk
+ $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
+
+endif
#
# Windows resource file
@@ -221,67 +335,74 @@ EXTRA_src_mpd_DEPENDENCIES = src/win32/mpd_win32_rc.$(OBJEXT)
src_mpd_LDFLAGS = -Wl,src/win32/mpd_win32_rc.$(OBJEXT)
endif
-if ENABLE_DESPOTIFY
-src_mpd_SOURCES += \
- src/DespotifyUtils.cxx src/DespotifyUtils.hxx
-endif
-
+if ENABLE_DATABASE
if ENABLE_INOTIFY
-src_mpd_SOURCES += \
- src/InotifyDomain.cxx src/InotifyDomain.hxx \
- src/InotifySource.cxx src/InotifySource.hxx \
- src/InotifyQueue.cxx src/InotifyQueue.hxx \
- src/InotifyUpdate.cxx src/InotifyUpdate.hxx
+libmpd_a_SOURCES += \
+ src/db/update/InotifyDomain.cxx src/db/update/InotifyDomain.hxx \
+ src/db/update/InotifySource.cxx src/db/update/InotifySource.hxx \
+ src/db/update/InotifyQueue.cxx src/db/update/InotifyQueue.hxx \
+ src/db/update/InotifyUpdate.cxx src/db/update/InotifyUpdate.hxx
+endif
endif
if ENABLE_SQLITE
-src_mpd_SOURCES += \
+libmpd_a_SOURCES += \
src/command/StickerCommands.cxx src/command/StickerCommands.hxx \
- src/StickerDatabase.cxx src/StickerDatabase.hxx \
- src/StickerPrint.cxx src/StickerPrint.hxx \
- src/SongSticker.cxx src/SongSticker.hxx
+ src/sticker/StickerDatabase.cxx src/sticker/StickerDatabase.hxx \
+ src/sticker/StickerPrint.cxx src/sticker/StickerPrint.hxx \
+ src/sticker/SongSticker.cxx src/sticker/SongSticker.hxx
endif
# Generic utility library
libutil_a_SOURCES = \
src/util/Macros.hxx \
+ src/util/Cast.hxx \
+ src/util/Clamp.hxx \
+ src/util/Alloc.cxx src/util/Alloc.hxx \
+ src/util/VarSize.hxx \
src/util/Error.cxx src/util/Error.hxx \
src/util/Domain.hxx \
src/util/ReusableArray.hxx \
src/util/ASCII.hxx \
+ src/util/UTF8.cxx src/util/UTF8.hxx \
src/util/CharUtil.hxx \
src/util/NumberParser.hxx \
src/util/StringUtil.cxx src/util/StringUtil.hxx \
+ src/util/SplitString.cxx src/util/SplitString.hxx \
src/util/FormatString.cxx src/util/FormatString.hxx \
src/util/Tokenizer.cxx src/util/Tokenizer.hxx \
+ src/util/TextFile.hxx \
src/util/UriUtil.cxx src/util/UriUtil.hxx \
src/util/Manual.hxx \
src/util/RefCount.hxx \
- src/util/fifo_buffer.c src/util/fifo_buffer.h \
- src/util/FifoBuffer.hxx \
+ src/util/StaticFifoBuffer.hxx \
+ src/util/ForeignFifoBuffer.hxx \
+ src/util/DynamicFifoBuffer.hxx \
+ src/util/ConstBuffer.hxx \
src/util/WritableBuffer.hxx \
- src/util/growing_fifo.c src/util/growing_fifo.h \
+ src/util/CircularBuffer.hxx \
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/OptionParser.cxx src/util/OptionParser.hxx \
+ src/util/OptionDef.hxx \
src/util/ByteReverse.cxx src/util/ByteReverse.hxx \
src/util/bit_reverse.c src/util/bit_reverse.h
# Multi-threading library
libthread_a_SOURCES = \
+ src/thread/Util.hxx \
+ src/thread/Name.hxx \
+ src/thread/Slack.hxx \
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/thread/Thread.cxx src/thread/Thread.hxx \
src/thread/Id.hxx
@@ -298,12 +419,18 @@ libsystem_a_SOURCES = \
src/system/EventFD.cxx src/system/EventFD.hxx \
src/system/SignalFD.cxx src/system/SignalFD.hxx \
src/system/EPollFD.cxx src/system/EPollFD.hxx \
+ src/system/PeriodClock.hxx \
src/system/Clock.cxx src/system/Clock.hxx
# Event loop library
libevent_a_SOURCES = \
src/event/WakeFD.hxx \
+ src/event/PollGroup.hxx \
+ src/event/PollGroupEPoll.hxx \
+ src/event/PollGroupPoll.hxx src/event/PollGroupPoll.cxx \
+ src/event/PollGroupWinSelect.hxx src/event/PollGroupWinSelect.cxx \
+ src/event/PollResultGeneric.hxx \
src/event/SignalMonitor.hxx src/event/SignalMonitor.cxx \
src/event/TimeoutMonitor.hxx src/event/TimeoutMonitor.cxx \
src/event/IdleMonitor.hxx src/event/IdleMonitor.cxx \
@@ -316,40 +443,87 @@ libevent_a_SOURCES = \
src/event/Call.hxx src/event/Call.cxx \
src/event/Loop.cxx src/event/Loop.hxx
+# UTF-8 library
+
+libicu_a_SOURCES = \
+ src/lib/icu/Collate.cxx src/lib/icu/Collate.hxx \
+ src/lib/icu/Error.cxx src/lib/icu/Error.hxx
+
+if HAVE_ICU
+libicu_a_SOURCES += \
+ src/lib/icu/Init.cxx src/lib/icu/Init.hxx
+endif
+
+libicu_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(ICU_CFLAGS)
+
+ICU_LDADD = libicu.a $(ICU_LIBS)
+
# PCM library
libpcm_a_SOURCES = \
+ src/pcm/Domain.cxx src/pcm/Domain.hxx \
+ src/pcm/Traits.hxx \
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \
src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \
- src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h \
- src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
- src/pcm/PcmDsdUsb.cxx src/pcm/PcmDsdUsb.hxx \
- src/pcm/PcmVolume.cxx src/pcm/PcmVolume.hxx \
+ src/pcm/PcmDop.cxx src/pcm/PcmDop.hxx \
+ src/pcm/Volume.cxx src/pcm/Volume.hxx \
src/pcm/PcmMix.cxx src/pcm/PcmMix.hxx \
src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \
src/pcm/PcmFormat.cxx src/pcm/PcmFormat.hxx \
- src/pcm/PcmResample.cxx src/pcm/PcmResample.hxx \
- src/pcm/PcmResampleFallback.cxx \
- src/pcm/PcmResampleInternal.hxx \
+ src/pcm/FloatConvert.hxx \
+ src/pcm/ShiftConvert.hxx \
+ src/pcm/Neon.hxx \
+ src/pcm/FormatConverter.cxx src/pcm/FormatConverter.hxx \
+ src/pcm/ChannelsConverter.cxx src/pcm/ChannelsConverter.hxx \
+ src/pcm/Resampler.hxx \
+ src/pcm/GlueResampler.cxx src/pcm/GlueResampler.hxx \
+ src/pcm/FallbackResampler.cxx src/pcm/FallbackResampler.hxx \
+ src/pcm/ConfiguredResampler.cxx src/pcm/ConfiguredResampler.hxx \
src/pcm/PcmDither.cxx src/pcm/PcmDither.hxx \
src/pcm/PcmPrng.hxx \
src/pcm/PcmUtils.hxx
libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(SOXR_CFLAGS) \
$(SAMPLERATE_CFLAGS)
PCM_LIBS = \
libpcm.a \
+ $(SOXR_LIBS) \
$(SAMPLERATE_LIBS)
+if ENABLE_DSD
+libpcm_a_SOURCES += \
+ src/pcm/PcmDsd.cxx src/pcm/PcmDsd.hxx \
+ src/pcm/dsd2pcm/dsd2pcm.c src/pcm/dsd2pcm/dsd2pcm.h
+endif
+
if HAVE_LIBSAMPLERATE
-libpcm_a_SOURCES += src/pcm/PcmResampleLibsamplerate.cxx
+libpcm_a_SOURCES += \
+ src/pcm/LibsamplerateResampler.cxx src/pcm/LibsamplerateResampler.hxx
+endif
+
+if HAVE_SOXR
+libpcm_a_SOURCES += \
+ src/pcm/SoxrResampler.cxx src/pcm/SoxrResampler.hxx
endif
# File system library
+FS_LIBS = libfs.a
+
libfs_a_SOURCES = \
+ src/fs/io/Reader.hxx \
+ src/fs/io/PeekReader.cxx src/fs/io/PeekReader.hxx \
+ src/fs/io/FileReader.cxx src/fs/io/FileReader.hxx \
+ src/fs/io/BufferedReader.cxx src/fs/io/BufferedReader.hxx \
+ src/fs/io/TextFile.cxx src/fs/io/TextFile.hxx \
+ src/fs/io/OutputStream.hxx \
+ src/fs/io/StdioOutputStream.hxx \
+ src/fs/io/FileOutputStream.cxx src/fs/io/FileOutputStream.hxx \
+ src/fs/io/BufferedOutputStream.cxx src/fs/io/BufferedOutputStream.hxx \
src/fs/Domain.cxx src/fs/Domain.hxx \
src/fs/Limits.hxx \
src/fs/Traits.cxx src/fs/Traits.hxx \
@@ -358,41 +532,187 @@ libfs_a_SOURCES = \
src/fs/Path.cxx src/fs/Path.hxx \
src/fs/AllocatedPath.cxx src/fs/AllocatedPath.hxx \
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
+ src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
+ src/fs/CheckFile.cxx src/fs/CheckFile.hxx \
src/fs/DirectoryReader.hxx
+libfs_a_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CFLAGS)
+
+if HAVE_ZLIB
+libfs_a_SOURCES += \
+ src/lib/zlib/Domain.cxx src/lib/zlib/Domain.hxx \
+ src/fs/io/GunzipReader.cxx src/fs/io/GunzipReader.hxx \
+ src/fs/io/AutoGunzipReader.cxx src/fs/io/AutoGunzipReader.hxx \
+ src/fs/io/GzipOutputStream.cxx src/fs/io/GzipOutputStream.hxx
+FS_LIBS += $(ZLIB_LIBS)
+endif
+
+# Storage library
+
+SMBCLIENT_SOURCES = \
+ src/lib/smbclient/Domain.cxx src/lib/smbclient/Domain.hxx \
+ src/lib/smbclient/Mutex.cxx src/lib/smbclient/Mutex.hxx \
+ src/lib/smbclient/Init.cxx src/lib/smbclient/Init.hxx
+
+NFS_SOURCES = \
+ src/lib/nfs/Callback.hxx \
+ src/lib/nfs/Cancellable.hxx \
+ src/lib/nfs/Lease.hxx \
+ src/lib/nfs/Connection.cxx src/lib/nfs/Connection.hxx \
+ src/lib/nfs/Manager.cxx src/lib/nfs/Manager.hxx \
+ src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \
+ src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \
+ src/lib/nfs/FileReader.cxx src/lib/nfs/FileReader.hxx \
+ src/lib/nfs/Blocking.cxx src/lib/nfs/Blocking.hxx \
+ src/lib/nfs/Domain.cxx src/lib/nfs/Domain.hxx
+
+if ENABLE_DATABASE
+
+noinst_LIBRARIES += libstorage.a
+
+libstorage_a_SOURCES = \
+ src/storage/StoragePlugin.hxx \
+ src/storage/Registry.cxx src/storage/Registry.hxx \
+ src/storage/StorageInterface.cxx src/storage/StorageInterface.hxx \
+ src/storage/CompositeStorage.cxx src/storage/CompositeStorage.hxx \
+ src/storage/MemoryDirectoryReader.cxx src/storage/MemoryDirectoryReader.hxx \
+ src/storage/Configured.cxx src/storage/Configured.hxx \
+ src/storage/plugins/LocalStorage.cxx src/storage/plugins/LocalStorage.hxx \
+ src/storage/FileInfo.hxx
+
+libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(NFS_CFLAGS) \
+ $(SMBCLIENT_CFLAGS)
+
+STORAGE_LIBS = \
+ libstorage.a \
+ $(NFS_LIBS) \
+ $(SMBCLIENT_LIBS)
+
+if ENABLE_SMBCLIENT
+libstorage_a_SOURCES += \
+ $(SMBCLIENT_SOURCES) \
+ src/storage/plugins/SmbclientStorage.cxx src/storage/plugins/SmbclientStorage.hxx
+endif
+
+if ENABLE_NFS
+libstorage_a_SOURCES += \
+ $(NFS_SOURCES) \
+ src/storage/plugins/NfsStorage.cxx src/storage/plugins/NfsStorage.hxx
+endif
+
+endif
+
+# neighbor plugins
+
+if ENABLE_NEIGHBOR_PLUGINS
+
+libmpd_a_SOURCES += \
+ src/command/NeighborCommands.cxx \
+ src/command/NeighborCommands.hxx
+
+noinst_LIBRARIES += libneighbor.a
+
+libneighbor_a_SOURCES = \
+ src/neighbor/Registry.cxx src/neighbor/Registry.hxx \
+ src/neighbor/Glue.cxx src/neighbor/Glue.hxx \
+ src/neighbor/Info.hxx \
+ src/neighbor/Listener.hxx \
+ src/neighbor/Explorer.hxx \
+ src/neighbor/NeighborPlugin.hxx
+
+libneighbor_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(SMBCLIENT_CFLAGS)
+
+if ENABLE_SMBCLIENT
+libneighbor_a_SOURCES += \
+ $(SMBCLIENT_SOURCES) \
+ src/neighbor/plugins/SmbclientNeighborPlugin.cxx src/neighbor/plugins/SmbclientNeighborPlugin.hxx
+endif
+
+NEIGHBOR_LIBS = \
+ $(SMBCLIENT_LIBS) \
+ libneighbor.a
+
+if HAVE_LIBUPNP
+libneighbor_a_SOURCES += \
+ $(UPNP_SOURCES) \
+ src/neighbor/plugins/UpnpNeighborPlugin.cxx src/neighbor/plugins/UpnpNeighborPlugin.hxx
+NEIGHBOR_LIBS += \
+ $(EXPAT_LIBS) \
+ $(UPNP_LIBS)
+endif
+
+endif
# database plugins
+if ENABLE_DATABASE
+
+noinst_LIBRARIES += libdb_plugins.a
+
libdb_plugins_a_SOURCES = \
- src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \
- src/DatabaseHelpers.cxx src/DatabaseHelpers.hxx \
- src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx
+ src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
+ src/db/Registry.cxx src/db/Registry.hxx \
+ src/db/Helpers.cxx src/db/Helpers.hxx \
+ src/db/UniqueTags.cxx src/db/UniqueTags.hxx \
+ src/db/plugins/simple/DatabaseSave.cxx \
+ src/db/plugins/simple/DatabaseSave.hxx \
+ src/db/plugins/simple/DirectorySave.cxx \
+ src/db/plugins/simple/DirectorySave.hxx \
+ src/db/plugins/LazyDatabase.cxx src/db/plugins/LazyDatabase.hxx \
+ src/db/plugins/simple/Directory.cxx \
+ src/db/plugins/simple/Directory.hxx \
+ src/db/plugins/simple/Song.cxx \
+ src/db/plugins/simple/Song.hxx \
+ src/db/plugins/simple/SongSort.cxx \
+ src/db/plugins/simple/SongSort.hxx \
+ src/db/plugins/simple/Mount.cxx \
+ src/db/plugins/simple/Mount.hxx \
+ src/db/plugins/simple/PrefixedLightSong.hxx \
+ src/db/plugins/simple/SimpleDatabasePlugin.cxx \
+ src/db/plugins/simple/SimpleDatabasePlugin.hxx
if HAVE_LIBMPDCLIENT
libdb_plugins_a_SOURCES += \
- src/db/ProxyDatabasePlugin.cxx src/db/ProxyDatabasePlugin.hxx
+ src/db/plugins/ProxyDatabasePlugin.cxx src/db/plugins/ProxyDatabasePlugin.hxx
endif
DB_LIBS = \
libdb_plugins.a \
$(LIBMPDCLIENT_LIBS)
+if HAVE_LIBUPNP
+libdb_plugins_a_SOURCES += \
+ $(UPNP_SOURCES) \
+ src/db/plugins/upnp/UpnpDatabasePlugin.cxx src/db/plugins/upnp/UpnpDatabasePlugin.hxx \
+ src/db/plugins/upnp/Tags.cxx src/db/plugins/upnp/Tags.hxx \
+ src/db/plugins/upnp/ContentDirectoryService.cxx \
+ src/db/plugins/upnp/Directory.cxx src/db/plugins/upnp/Directory.hxx \
+ src/db/plugins/upnp/Object.cxx src/db/plugins/upnp/Object.hxx
+DB_LIBS += \
+ $(EXPAT_LIBS) \
+ $(UPNP_LIBS)
+endif
+
+endif
+
# archive plugins
if ENABLE_ARCHIVE
noinst_LIBRARIES += libarchive.a
-src_mpd_SOURCES += \
- src/UpdateArchive.cxx src/UpdateArchive.hxx
+libmpd_a_SOURCES += \
+ src/db/update/Archive.cxx
libarchive_a_SOURCES = \
- src/ArchiveDomain.cxx src/ArchiveDomain.hxx \
- 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
+ src/archive/ArchiveDomain.cxx src/archive/ArchiveDomain.hxx \
+ src/archive/ArchiveLookup.cxx src/archive/ArchiveLookup.hxx \
+ src/archive/ArchiveList.cxx src/archive/ArchiveList.hxx \
+ src/archive/ArchivePlugin.cxx src/archive/ArchivePlugin.hxx \
+ src/archive/ArchiveVisitor.hxx \
+ src/archive/ArchiveFile.hxx \
+ src/input/plugins/ArchiveInputPlugin.cxx src/input/plugins/ArchiveInputPlugin.hxx
libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(BZ2_CFLAGS) \
$(ISO9660_CFLAGS) \
@@ -406,20 +726,20 @@ ARCHIVE_LIBS = \
if HAVE_BZ2
libarchive_a_SOURCES += \
- src/archive/Bzip2ArchivePlugin.cxx \
- src/archive/Bzip2ArchivePlugin.hxx
+ src/archive/plugins/Bzip2ArchivePlugin.cxx \
+ src/archive/plugins/Bzip2ArchivePlugin.hxx
endif
if HAVE_ZZIP
libarchive_a_SOURCES += \
- src/archive/ZzipArchivePlugin.cxx \
- src/archive/ZzipArchivePlugin.hxx
+ src/archive/plugins/ZzipArchivePlugin.cxx \
+ src/archive/plugins/ZzipArchivePlugin.hxx
endif
if HAVE_ISO9660
libarchive_a_SOURCES += \
- src/archive/Iso9660ArchivePlugin.cxx \
- src/archive/Iso9660ArchivePlugin.hxx
+ src/archive/plugins/Iso9660ArchivePlugin.cxx \
+ src/archive/plugins/Iso9660ArchivePlugin.hxx
endif
else
@@ -429,15 +749,15 @@ endif
# configuration library
libconf_a_SOURCES = \
- src/ConfigDefaults.hxx \
- 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/ConfigError.cxx src/ConfigError.hxx \
- src/ConfigOption.hxx
+ src/config/ConfigDefaults.hxx \
+ src/config/ConfigPath.cxx src/config/ConfigPath.hxx \
+ src/config/ConfigData.cxx src/config/ConfigData.hxx \
+ src/config/ConfigParser.cxx src/config/ConfigParser.hxx \
+ src/config/ConfigGlobal.cxx src/config/ConfigGlobal.hxx \
+ src/config/ConfigFile.cxx src/config/ConfigFile.hxx \
+ src/config/ConfigTemplates.cxx src/config/ConfigTemplates.hxx \
+ src/config/ConfigError.cxx src/config/ConfigError.hxx \
+ src/config/ConfigOption.hxx
# tag plugins
@@ -459,6 +779,10 @@ libtag_a_SOURCES =\
src/tag/TagString.cxx src/tag/TagString.hxx \
src/tag/TagPool.cxx src/tag/TagPool.hxx \
src/tag/TagTable.cxx src/tag/TagTable.hxx \
+ src/tag/Set.cxx src/tag/Set.hxx \
+ src/tag/VorbisComment.cxx src/tag/VorbisComment.hxx \
+ src/tag/ReplayGain.cxx src/tag/ReplayGain.hxx \
+ src/tag/MixRamp.cxx src/tag/MixRamp.hxx \
src/tag/ApeLoader.cxx src/tag/ApeLoader.hxx \
src/tag/ApeReplayGain.cxx src/tag/ApeReplayGain.hxx \
src/tag/ApeTag.cxx src/tag/ApeTag.hxx
@@ -471,15 +795,27 @@ libtag_a_SOURCES += \
src/tag/Aiff.cxx src/tag/Aiff.hxx
endif
+# ffmpeg
+
+if HAVE_FFMPEG
+noinst_LIBRARIES += libffmpeg.a
+libffmpeg_a_SOURCES = \
+ src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \
+ src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx
+libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(FFMPEG_CFLAGS)
+FFMPEG_LIBS2 = libffmpeg.a $(FFMPEG_LIBS)
+endif
+
# decoder plugins
-libdecoder_plugins_a_SOURCES = \
- src/decoder/PcmDecoderPlugin.cxx \
- src/decoder/PcmDecoderPlugin.hxx \
- src/DecoderBuffer.cxx src/DecoderBuffer.hxx \
- src/DecoderPlugin.cxx \
- src/DecoderList.cxx src/DecoderList.hxx
-libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
+libdecoder_a_SOURCES = \
+ src/decoder/plugins/PcmDecoderPlugin.cxx \
+ src/decoder/plugins/PcmDecoderPlugin.hxx \
+ src/decoder/DecoderBuffer.cxx src/decoder/DecoderBuffer.hxx \
+ src/decoder/DecoderPlugin.cxx \
+ src/decoder/DecoderList.cxx src/decoder/DecoderList.hxx
+libdecoder_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(SNDFILE_CFLAGS) \
@@ -499,7 +835,7 @@ libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(FAAD_CFLAGS)
DECODER_LIBS = \
- libdecoder_plugins.a \
+ libdecoder.a \
$(VORBIS_LIBS) $(TREMOR_LIBS) \
$(FLAC_LIBS) \
$(SNDFILE_LIBS) \
@@ -512,156 +848,154 @@ DECODER_LIBS = \
$(MAD_LIBS) \
$(MPG123_LIBS) \
$(OPUS_LIBS) \
- $(FFMPEG_LIBS) \
+ $(FFMPEG_LIBS2) \
$(MPCDEC_LIBS) \
$(ADPLUG_LIBS) \
$(FAAD_LIBS)
-DECODER_SRC =
-
if ENABLE_DSD
-libdecoder_plugins_a_SOURCES += \
- src/decoder/DsdiffDecoderPlugin.cxx \
- src/decoder/DsdiffDecoderPlugin.hxx \
- src/decoder/DsfDecoderPlugin.cxx \
- src/decoder/DsfDecoderPlugin.hxx \
- src/decoder/DsdLib.cxx \
- src/decoder/DsdLib.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/DsdiffDecoderPlugin.cxx \
+ src/decoder/plugins/DsdiffDecoderPlugin.hxx \
+ src/decoder/plugins/DsfDecoderPlugin.cxx \
+ src/decoder/plugins/DsfDecoderPlugin.hxx \
+ src/decoder/plugins/DsdLib.cxx \
+ src/decoder/plugins/DsdLib.hxx
endif
if HAVE_MAD
-libdecoder_plugins_a_SOURCES += \
- src/decoder/MadDecoderPlugin.cxx \
- src/decoder/MadDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/MadDecoderPlugin.cxx \
+ src/decoder/plugins/MadDecoderPlugin.hxx
endif
if HAVE_MPG123
-libdecoder_plugins_a_SOURCES += \
- src/decoder/Mpg123DecoderPlugin.cxx \
- src/decoder/Mpg123DecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/Mpg123DecoderPlugin.cxx \
+ src/decoder/plugins/Mpg123DecoderPlugin.hxx
endif
if HAVE_MPCDEC
-libdecoder_plugins_a_SOURCES += \
- src/decoder/MpcdecDecoderPlugin.cxx \
- src/decoder/MpcdecDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/MpcdecDecoderPlugin.cxx \
+ src/decoder/plugins/MpcdecDecoderPlugin.hxx
endif
if HAVE_OPUS
-libdecoder_plugins_a_SOURCES += \
- src/decoder/OggUtil.cxx \
- src/decoder/OggUtil.hxx \
- src/decoder/OggSyncState.hxx \
- src/decoder/OggFind.cxx src/decoder/OggFind.hxx \
- src/decoder/OpusDomain.cxx src/decoder/OpusDomain.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
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/OggUtil.cxx \
+ src/decoder/plugins/OggUtil.hxx \
+ src/decoder/plugins/OggSyncState.hxx \
+ src/decoder/plugins/OggFind.cxx src/decoder/plugins/OggFind.hxx \
+ src/decoder/plugins/OpusDomain.cxx src/decoder/plugins/OpusDomain.hxx \
+ src/decoder/plugins/OpusReader.hxx \
+ src/decoder/plugins/OpusHead.hxx \
+ src/decoder/plugins/OpusHead.cxx \
+ src/decoder/plugins/OpusTags.cxx \
+ src/decoder/plugins/OpusTags.hxx \
+ src/decoder/plugins/OpusDecoderPlugin.cxx \
+ src/decoder/plugins/OpusDecoderPlugin.h
endif
if HAVE_WAVPACK
-libdecoder_plugins_a_SOURCES += \
- src/decoder/WavpackDecoderPlugin.cxx \
- src/decoder/WavpackDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/WavpackDecoderPlugin.cxx \
+ src/decoder/plugins/WavpackDecoderPlugin.hxx
endif
if HAVE_ADPLUG
-libdecoder_plugins_a_SOURCES += \
- src/decoder/AdPlugDecoderPlugin.cxx \
- src/decoder/AdPlugDecoderPlugin.h
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/AdPlugDecoderPlugin.cxx \
+ src/decoder/plugins/AdPlugDecoderPlugin.h
endif
if HAVE_FAAD
-libdecoder_plugins_a_SOURCES += \
- src/decoder/FaadDecoderPlugin.cxx src/decoder/FaadDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/FaadDecoderPlugin.cxx src/decoder/plugins/FaadDecoderPlugin.hxx
endif
if HAVE_XIPH
-libdecoder_plugins_a_SOURCES += \
- src/decoder/XiphTags.cxx src/decoder/XiphTags.hxx \
- src/decoder/OggCodec.cxx src/decoder/OggCodec.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/XiphTags.cxx src/decoder/plugins/XiphTags.hxx \
+ src/decoder/plugins/OggCodec.cxx src/decoder/plugins/OggCodec.hxx
endif
if ENABLE_VORBIS_DECODER
-libdecoder_plugins_a_SOURCES += \
- src/decoder/VorbisDomain.cxx src/decoder/VorbisDomain.hxx \
- src/decoder/VorbisComments.cxx src/decoder/VorbisComments.hxx \
- src/decoder/VorbisDecoderPlugin.cxx src/decoder/VorbisDecoderPlugin.h
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/VorbisDomain.cxx src/decoder/plugins/VorbisDomain.hxx \
+ src/decoder/plugins/VorbisComments.cxx src/decoder/plugins/VorbisComments.hxx \
+ src/decoder/plugins/VorbisDecoderPlugin.cxx src/decoder/plugins/VorbisDecoderPlugin.h
endif
if HAVE_FLAC
-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/FlacDomain.cxx src/decoder/FlacDomain.hxx \
- src/decoder/FlacCommon.cxx src/decoder/FlacCommon.hxx \
- src/decoder/FlacDecoderPlugin.cxx \
- src/decoder/FlacDecoderPlugin.h
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/FlacInput.cxx src/decoder/plugins/FlacInput.hxx \
+ src/decoder/plugins/FlacIOHandle.cxx src/decoder/plugins/FlacIOHandle.hxx \
+ src/decoder/plugins/FlacMetadata.cxx src/decoder/plugins/FlacMetadata.hxx \
+ src/decoder/plugins/FlacPcm.cxx src/decoder/plugins/FlacPcm.hxx \
+ src/decoder/plugins/FlacDomain.cxx src/decoder/plugins/FlacDomain.hxx \
+ src/decoder/plugins/FlacCommon.cxx src/decoder/plugins/FlacCommon.hxx \
+ src/decoder/plugins/FlacDecoderPlugin.cxx \
+ src/decoder/plugins/FlacDecoderPlugin.h
endif
if HAVE_AUDIOFILE
-libdecoder_plugins_a_SOURCES += \
- src/decoder/AudiofileDecoderPlugin.cxx \
- src/decoder/AudiofileDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/AudiofileDecoderPlugin.cxx \
+ src/decoder/plugins/AudiofileDecoderPlugin.hxx
endif
if ENABLE_MIKMOD_DECODER
-libdecoder_plugins_a_SOURCES += \
- src/decoder/MikmodDecoderPlugin.cxx \
- src/decoder/MikmodDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/MikmodDecoderPlugin.cxx \
+ src/decoder/plugins/MikmodDecoderPlugin.hxx
endif
if HAVE_MODPLUG
libmodplug_decoder_plugin_a_SOURCES = \
- src/decoder/ModplugDecoderPlugin.cxx \
- src/decoder/ModplugDecoderPlugin.hxx
+ src/decoder/plugins/ModplugDecoderPlugin.cxx \
+ src/decoder/plugins/ModplugDecoderPlugin.hxx
libmodplug_decoder_plugin_a_CXXFLAGS = $(AM_CXXFLAGS) $(MODPLUG_CFLAGS)
-libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
+libmodplug_decoder_plugin_a_CPPFLAGS = $(AM_CPPFLAGS)
noinst_LIBRARIES += libmodplug_decoder_plugin.a
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if ENABLE_SIDPLAY
-libdecoder_plugins_a_SOURCES += \
- src/decoder/SidplayDecoderPlugin.cxx \
- src/decoder/SidplayDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/SidplayDecoderPlugin.cxx \
+ src/decoder/plugins/SidplayDecoderPlugin.hxx
endif
if ENABLE_FLUIDSYNTH
-libdecoder_plugins_a_SOURCES += \
- src/decoder/FluidsynthDecoderPlugin.cxx \
- src/decoder/FluidsynthDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/FluidsynthDecoderPlugin.cxx \
+ src/decoder/plugins/FluidsynthDecoderPlugin.hxx
endif
if ENABLE_WILDMIDI
-libdecoder_plugins_a_SOURCES += \
- src/decoder/WildmidiDecoderPlugin.cxx \
- src/decoder/WildmidiDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/WildmidiDecoderPlugin.cxx \
+ src/decoder/plugins/WildmidiDecoderPlugin.hxx
endif
if HAVE_FFMPEG
-libdecoder_plugins_a_SOURCES += \
- src/decoder/FfmpegMetaData.cxx \
- src/decoder/FfmpegMetaData.hxx \
- src/decoder/FfmpegDecoderPlugin.cxx \
- src/decoder/FfmpegDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/FfmpegMetaData.cxx \
+ src/decoder/plugins/FfmpegMetaData.hxx \
+ src/decoder/plugins/FfmpegDecoderPlugin.cxx \
+ src/decoder/plugins/FfmpegDecoderPlugin.hxx
endif
if ENABLE_SNDFILE
-libdecoder_plugins_a_SOURCES += \
- src/decoder/SndfileDecoderPlugin.cxx \
- src/decoder/SndfileDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/SndfileDecoderPlugin.cxx \
+ src/decoder/plugins/SndfileDecoderPlugin.hxx
endif
if HAVE_GME
-libdecoder_plugins_a_SOURCES += \
- src/decoder/GmeDecoderPlugin.cxx src/decoder/GmeDecoderPlugin.hxx
+libdecoder_a_SOURCES += \
+ src/decoder/plugins/GmeDecoderPlugin.cxx src/decoder/plugins/GmeDecoderPlugin.hxx
endif
# encoder plugins
@@ -675,6 +1009,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(TWOLAME_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(OPUS_CFLAGS) \
+ $(SHINE_CFLAGS) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
@@ -683,54 +1018,64 @@ ENCODER_LIBS = \
$(TWOLAME_LIBS) \
$(FLAC_LIBS) \
$(OPUS_LIBS) \
+ $(SHINE_LIBS) \
$(VORBISENC_LIBS)
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
+ src/encoder/EncoderAPI.hxx \
+ src/encoder/EncoderPlugin.hxx \
+ src/encoder/plugins/OggStream.hxx \
+ src/encoder/plugins/NullEncoderPlugin.cxx \
+ src/encoder/plugins/NullEncoderPlugin.hxx \
+ src/encoder/EncoderList.cxx src/encoder/EncoderList.hxx
if HAVE_OGG_ENCODER
libencoder_plugins_a_SOURCES += \
- src/encoder/OggSerial.cxx src/encoder/OggSerial.hxx \
- src/encoder/OggStream.hxx
+ src/encoder/plugins/OggSerial.cxx \
+ src/encoder/plugins/OggSerial.hxx \
+ src/encoder/plugins/OggStream.hxx
endif
if ENABLE_WAVE_ENCODER
libencoder_plugins_a_SOURCES += \
- src/encoder/WaveEncoderPlugin.cxx \
- src/encoder/WaveEncoderPlugin.hxx
+ src/encoder/plugins/WaveEncoderPlugin.cxx \
+ src/encoder/plugins/WaveEncoderPlugin.hxx
endif
if ENABLE_VORBIS_ENCODER
libencoder_plugins_a_SOURCES += \
- src/encoder/VorbisEncoderPlugin.cxx \
- src/encoder/VorbisEncoderPlugin.hxx
+ src/encoder/plugins/VorbisEncoderPlugin.cxx \
+ src/encoder/plugins/VorbisEncoderPlugin.hxx
endif
if HAVE_OPUS
libencoder_plugins_a_SOURCES += \
- src/encoder/OpusEncoderPlugin.cxx \
- src/encoder/OpusEncoderPlugin.hxx
+ src/encoder/plugins/OpusEncoderPlugin.cxx \
+ src/encoder/plugins/OpusEncoderPlugin.hxx
endif
if ENABLE_LAME_ENCODER
libencoder_plugins_a_SOURCES += \
- src/encoder/LameEncoderPlugin.cxx \
- src/encoder/LameEncoderPlugin.hxx
+ src/encoder/plugins/LameEncoderPlugin.cxx \
+ src/encoder/plugins/LameEncoderPlugin.hxx
endif
if ENABLE_TWOLAME_ENCODER
libencoder_plugins_a_SOURCES += \
- src/encoder/TwolameEncoderPlugin.cxx \
- src/encoder/TwolameEncoderPlugin.hxx
+ src/encoder/plugins/TwolameEncoderPlugin.cxx \
+ src/encoder/plugins/TwolameEncoderPlugin.hxx
endif
if ENABLE_FLAC_ENCODER
libencoder_plugins_a_SOURCES += \
- src/encoder/FlacEncoderPlugin.cxx src/encoder/FlacEncoderPlugin.hxx
+ src/encoder/plugins/FlacEncoderPlugin.cxx \
+ src/encoder/plugins/FlacEncoderPlugin.hxx
+endif
+
+if ENABLE_SHINE_ENCODER
+libencoder_plugins_a_SOURCES += \
+ src/encoder/plugins/ShineEncoderPlugin.cxx \
+ src/encoder/plugins/ShineEncoderPlugin.hxx
endif
else
@@ -739,18 +1084,18 @@ endif
if HAVE_ZEROCONF
-src_mpd_SOURCES += \
- src/ZeroconfInternal.hxx \
- src/ZeroconfGlue.cxx src/ZeroconfGlue.hxx
+libmpd_a_SOURCES += \
+ src/zeroconf/ZeroconfInternal.hxx \
+ src/zeroconf/ZeroconfGlue.cxx src/zeroconf/ZeroconfGlue.hxx
if HAVE_AVAHI
-src_mpd_SOURCES += \
- src/AvahiPoll.cxx src/AvahiPoll.hxx \
- src/ZeroconfAvahi.cxx src/ZeroconfAvahi.hxx
+libmpd_a_SOURCES += \
+ src/zeroconf/AvahiPoll.cxx src/zeroconf/AvahiPoll.hxx \
+ src/zeroconf/ZeroconfAvahi.cxx src/zeroconf/ZeroconfAvahi.hxx
endif
if HAVE_BONJOUR
-src_mpd_SOURCES += src/ZeroconfBonjour.cxx src/ZeroconfBonjour.hxx
+libmpd_a_SOURCES += src/zeroconf/ZeroconfBonjour.cxx src/zeroconf/ZeroconfBonjour.hxx
endif
endif
@@ -759,15 +1104,25 @@ endif
#
libinput_a_SOURCES = \
- src/InputInit.cxx src/InputInit.hxx \
- src/InputRegistry.cxx src/InputRegistry.hxx \
- src/InputStream.cxx src/InputStream.hxx \
- src/InputPlugin.hxx \
- src/input/RewindInputPlugin.cxx src/input/RewindInputPlugin.hxx \
- src/input/FileInputPlugin.cxx src/input/FileInputPlugin.hxx
+ src/input/Domain.cxx src/input/Domain.hxx \
+ src/input/Init.cxx src/input/Init.hxx \
+ src/input/Registry.cxx src/input/Registry.hxx \
+ src/input/Open.cxx \
+ src/input/LocalOpen.cxx src/input/LocalOpen.hxx \
+ src/input/Offset.hxx \
+ src/input/InputStream.cxx src/input/InputStream.hxx \
+ src/input/InputPlugin.hxx \
+ src/input/TextInputStream.cxx src/input/TextInputStream.hxx \
+ src/input/ThreadInputStream.cxx src/input/ThreadInputStream.hxx \
+ src/input/AsyncInputStream.cxx src/input/AsyncInputStream.hxx \
+ src/input/ProxyInputStream.cxx src/input/ProxyInputStream.hxx \
+ src/input/plugins/RewindInputPlugin.cxx src/input/plugins/RewindInputPlugin.hxx \
+ src/input/plugins/FileInputPlugin.cxx src/input/plugins/FileInputPlugin.hxx
libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
+ $(SMBCLIENT_CFLAGS) \
+ $(NFS_CFLAGS) \
$(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(DESPOTIFY_CFLAGS) \
@@ -776,37 +1131,62 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
INPUT_LIBS = \
libinput.a \
$(CURL_LIBS) \
+ $(SMBCLIENT_LIBS) \
+ $(NFS_LIBS) \
$(CDIO_PARANOIA_LIBS) \
- $(FFMPEG_LIBS) \
+ $(FFMPEG_LIBS2) \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
+if HAVE_ALSA
+libinput_a_SOURCES += \
+ src/input/plugins/AlsaInputPlugin.cxx \
+ src/input/plugins/AlsaInputPlugin.hxx
+INPUT_LIBS += $(ALSA_LIBS)
+endif
+
+
if ENABLE_CURL
libinput_a_SOURCES += \
- src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \
+ src/input/IcyInputStream.cxx src/input/IcyInputStream.hxx \
+ src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \
src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx
endif
+if ENABLE_SMBCLIENT
+libinput_a_SOURCES += \
+ $(SMBCLIENT_SOURCES) \
+ src/input/plugins/SmbclientInputPlugin.cxx src/input/plugins/SmbclientInputPlugin.hxx
+endif
+
+if ENABLE_NFS
+libinput_a_SOURCES += \
+ $(NFS_SOURCES) \
+ src/input/plugins/NfsInputPlugin.cxx src/input/plugins/NfsInputPlugin.hxx
+endif
+
if ENABLE_CDIO_PARANOIA
libinput_a_SOURCES += \
- src/input/CdioParanoiaInputPlugin.cxx \
- src/input/CdioParanoiaInputPlugin.hxx
+ src/input/plugins/CdioParanoiaInputPlugin.cxx \
+ src/input/plugins/CdioParanoiaInputPlugin.hxx
endif
if HAVE_FFMPEG
libinput_a_SOURCES += \
- src/input/FfmpegInputPlugin.cxx src/input/FfmpegInputPlugin.hxx
+ src/input/plugins/FfmpegInputPlugin.cxx src/input/plugins/FfmpegInputPlugin.hxx
endif
if ENABLE_MMS
libinput_a_SOURCES += \
- src/input/MmsInputPlugin.cxx src/input/MmsInputPlugin.hxx
+ src/input/plugins/MmsInputPlugin.cxx src/input/plugins/MmsInputPlugin.hxx
endif
if ENABLE_DESPOTIFY
libinput_a_SOURCES += \
- src/input/DespotifyInputPlugin.cxx \
- src/input/DespotifyInputPlugin.hxx
+ src/lib/despotify/DespotifyUtils.cxx \
+ src/lib/despotify/DespotifyUtils.hxx \
+ src/input/plugins/DespotifyInputPlugin.cxx \
+ src/input/plugins/DespotifyInputPlugin.hxx
endif
@@ -831,128 +1211,155 @@ OUTPUT_LIBS = \
$(SHOUT_LIBS)
OUTPUT_API_SRC = \
- 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.cxx 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
+ src/output/OutputAPI.hxx \
+ src/output/Internal.hxx \
+ src/output/Registry.cxx src/output/Registry.hxx \
+ src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \
+ src/output/OutputThread.cxx \
+ src/output/Domain.cxx src/output/Domain.hxx \
+ src/output/OutputControl.cxx \
+ src/output/OutputState.cxx src/output/OutputState.hxx \
+ src/output/OutputPrint.cxx src/output/OutputPrint.hxx \
+ src/output/OutputCommand.cxx src/output/OutputCommand.hxx \
+ src/output/OutputPlugin.cxx src/output/OutputPlugin.hxx \
+ src/output/Finish.cxx \
+ src/output/Init.cxx
liboutput_plugins_a_SOURCES = \
- src/output/NullOutputPlugin.cxx \
- src/output/NullOutputPlugin.hxx
+ src/output/Timer.cxx src/output/Timer.hxx \
+ src/output/plugins/NullOutputPlugin.cxx \
+ src/output/plugins/NullOutputPlugin.hxx
MIXER_LIBS = \
libmixer_plugins.a \
$(PULSE_LIBS)
MIXER_API_SRC = \
- src/MixerPlugin.hxx \
- src/MixerList.hxx \
- src/MixerControl.cxx src/MixerControl.hxx \
- src/MixerType.cxx src/MixerType.hxx \
- src/MixerAll.cxx src/MixerAll.hxx \
- src/MixerInternal.hxx
+ src/mixer/Listener.hxx \
+ src/mixer/MixerPlugin.hxx \
+ src/mixer/MixerList.hxx \
+ src/mixer/MixerControl.cxx src/mixer/MixerControl.hxx \
+ src/mixer/MixerType.cxx src/mixer/MixerType.hxx \
+ src/mixer/MixerAll.cxx \
+ src/mixer/MixerInternal.hxx
libmixer_plugins_a_SOURCES = \
- src/mixer/SoftwareMixerPlugin.cxx \
- src/mixer/SoftwareMixerPlugin.hxx
+ src/mixer/plugins/SoftwareMixerPlugin.cxx \
+ src/mixer/plugins/SoftwareMixerPlugin.hxx
libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(ALSA_CFLAGS) \
$(PULSE_CFLAGS)
if HAVE_ALSA
liboutput_plugins_a_SOURCES += \
- src/output/AlsaOutputPlugin.cxx \
- src/output/AlsaOutputPlugin.hxx
-libmixer_plugins_a_SOURCES += src/mixer/AlsaMixerPlugin.cxx
+ src/output/plugins/AlsaOutputPlugin.cxx \
+ src/output/plugins/AlsaOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/plugins/AlsaMixerPlugin.cxx
+endif
+
+if ANDROID
+liboutput_plugins_a_SOURCES += \
+ src/output/plugins/sles/Object.hxx \
+ src/output/plugins/sles/Engine.hxx \
+ src/output/plugins/sles/Play.hxx \
+ src/output/plugins/sles/AndroidSimpleBufferQueue.hxx \
+ src/output/plugins/sles/SlesOutputPlugin.cxx \
+ src/output/plugins/sles/SlesOutputPlugin.hxx
+OUTPUT_LIBS += -lOpenSLES
endif
if HAVE_ROAR
liboutput_plugins_a_SOURCES += \
- src/output/RoarOutputPlugin.cxx src/output/RoarOutputPlugin.hxx
-libmixer_plugins_a_SOURCES += src/mixer/RoarMixerPlugin.cxx
+ src/output/plugins/RoarOutputPlugin.cxx \
+ src/output/plugins/RoarOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/plugins/RoarMixerPlugin.cxx
endif
if HAVE_AO
liboutput_plugins_a_SOURCES += \
- src/output/AoOutputPlugin.cxx src/output/AoOutputPlugin.hxx
+ src/output/plugins/AoOutputPlugin.cxx \
+ src/output/plugins/AoOutputPlugin.hxx
endif
if HAVE_FIFO
liboutput_plugins_a_SOURCES += \
- src/output/FifoOutputPlugin.cxx src/output/FifoOutputPlugin.hxx
+ src/output/plugins/FifoOutputPlugin.cxx \
+ src/output/plugins/FifoOutputPlugin.hxx
endif
if ENABLE_PIPE_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/PipeOutputPlugin.cxx src/output/PipeOutputPlugin.hxx
+ src/output/plugins/PipeOutputPlugin.cxx \
+ src/output/plugins/PipeOutputPlugin.hxx
endif
if HAVE_JACK
liboutput_plugins_a_SOURCES += \
- src/output/JackOutputPlugin.cxx src/output/JackOutputPlugin.hxx
+ src/output/plugins/JackOutputPlugin.cxx \
+ src/output/plugins/JackOutputPlugin.hxx
endif
if HAVE_OSS
liboutput_plugins_a_SOURCES += \
- src/output/OssOutputPlugin.cxx \
- src/output/OssOutputPlugin.hxx
-libmixer_plugins_a_SOURCES += src/mixer/OssMixerPlugin.cxx
+ src/output/plugins/OssOutputPlugin.cxx \
+ src/output/plugins/OssOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/plugins/OssMixerPlugin.cxx
endif
if HAVE_OPENAL
liboutput_plugins_a_SOURCES += \
- src/output/OpenALOutputPlugin.cxx src/output/OpenALOutputPlugin.hxx
+ src/output/plugins/OpenALOutputPlugin.cxx \
+ src/output/plugins/OpenALOutputPlugin.hxx
endif
if HAVE_OSX
liboutput_plugins_a_SOURCES += \
- src/output/OSXOutputPlugin.cxx \
- src/output/OSXOutputPlugin.hxx
+ src/output/plugins/OSXOutputPlugin.cxx \
+ src/output/plugins/OSXOutputPlugin.hxx
endif
if HAVE_PULSE
liboutput_plugins_a_SOURCES += \
- src/output/PulseOutputPlugin.cxx src/output/PulseOutputPlugin.hxx
+ src/output/plugins/PulseOutputPlugin.cxx \
+ src/output/plugins/PulseOutputPlugin.hxx
libmixer_plugins_a_SOURCES += \
- src/mixer/PulseMixerPlugin.cxx src/mixer/PulseMixerPlugin.hxx
+ src/mixer/plugins/PulseMixerPlugin.cxx src/mixer/plugins/PulseMixerPlugin.hxx
endif
if HAVE_SHOUT
liboutput_plugins_a_SOURCES += \
- src/output/ShoutOutputPlugin.cxx src/output/ShoutOutputPlugin.hxx
+ src/output/plugins/ShoutOutputPlugin.cxx \
+ src/output/plugins/ShoutOutputPlugin.hxx
endif
if ENABLE_RECORDER_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/RecorderOutputPlugin.cxx src/output/RecorderOutputPlugin.hxx
+ src/output/plugins/RecorderOutputPlugin.cxx \
+ src/output/plugins/RecorderOutputPlugin.hxx
endif
if ENABLE_HTTPD_OUTPUT
liboutput_plugins_a_SOURCES += \
- 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
+ src/output/plugins/httpd/IcyMetaDataServer.cxx \
+ src/output/plugins/httpd/IcyMetaDataServer.hxx \
+ src/output/plugins/httpd/Page.cxx src/output/plugins/httpd/Page.hxx \
+ src/output/plugins/httpd/HttpdInternal.hxx \
+ src/output/plugins/httpd/HttpdClient.cxx \
+ src/output/plugins/httpd/HttpdClient.hxx \
+ src/output/plugins/httpd/HttpdOutputPlugin.cxx \
+ src/output/plugins/httpd/HttpdOutputPlugin.hxx
endif
if ENABLE_SOLARIS_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/SolarisOutputPlugin.cxx src/output/SolarisOutputPlugin.hxx
+ src/output/plugins/SolarisOutputPlugin.cxx src/output/plugins/SolarisOutputPlugin.hxx
endif
if ENABLE_WINMM_OUTPUT
liboutput_plugins_a_SOURCES += \
- src/output/WinmmOutputPlugin.cxx src/output/WinmmOutputPlugin.hxx
-libmixer_plugins_a_SOURCES += src/mixer/WinmmMixerPlugin.cxx
+ src/output/plugins/WinmmOutputPlugin.cxx \
+ src/output/plugins/WinmmOutputPlugin.hxx
+libmixer_plugins_a_SOURCES += src/mixer/plugins/WinmmMixerPlugin.cxx
endif
@@ -961,65 +1368,85 @@ endif
#
libplaylist_plugins_a_SOURCES = \
- src/PlaylistPlugin.hxx \
- src/SongEnumerator.hxx \
- src/MemorySongEnumerator.cxx src/MemorySongEnumerator.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
+ src/playlist/PlaylistPlugin.hxx \
+ src/playlist/SongEnumerator.hxx \
+ src/playlist/CloseSongEnumerator.cxx \
+ src/playlist/CloseSongEnumerator.hxx \
+ src/playlist/MemorySongEnumerator.cxx \
+ src/playlist/MemorySongEnumerator.hxx \
+ src/playlist/cue/CueParser.cxx src/playlist/cue/CueParser.hxx \
+ src/playlist/plugins/ExtM3uPlaylistPlugin.cxx \
+ src/playlist/plugins/ExtM3uPlaylistPlugin.hxx \
+ src/playlist/plugins/M3uPlaylistPlugin.cxx \
+ src/playlist/plugins/M3uPlaylistPlugin.hxx \
+ src/playlist/plugins/CuePlaylistPlugin.cxx \
+ src/playlist/plugins/CuePlaylistPlugin.hxx \
+ src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \
+ src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx \
+ src/playlist/PlaylistRegistry.cxx src/playlist/PlaylistRegistry.hxx
libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
+ $(EXPAT_CFLAGS) \
$(YAJL_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS))
PLAYLIST_LIBS = \
libplaylist_plugins.a \
+ $(EXPAT_LIBS) \
$(FLAC_LIBS)
if ENABLE_DESPOTIFY
libplaylist_plugins_a_SOURCES += \
- src/playlist/DespotifyPlaylistPlugin.cxx \
- src/playlist/DespotifyPlaylistPlugin.hxx
+ src/lib/despotify/DespotifyUtils.cxx \
+ src/lib/despotify/DespotifyUtils.hxx \
+ src/playlist/plugins/DespotifyPlaylistPlugin.cxx \
+ src/playlist/plugins/DespotifyPlaylistPlugin.hxx
endif
if ENABLE_SOUNDCLOUD
libplaylist_plugins_a_SOURCES += \
- src/playlist/SoundCloudPlaylistPlugin.cxx \
- src/playlist/SoundCloudPlaylistPlugin.hxx
+ src/playlist/plugins/SoundCloudPlaylistPlugin.cxx \
+ src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
PLAYLIST_LIBS += $(YAJL_LIBS)
endif
+if HAVE_EXPAT
+libplaylist_plugins_a_SOURCES += \
+ src/lib/expat/ExpatParser.cxx src/lib/expat/ExpatParser.hxx \
+ src/playlist/plugins/XspfPlaylistPlugin.cxx \
+ src/playlist/plugins/XspfPlaylistPlugin.hxx \
+ src/playlist/plugins/AsxPlaylistPlugin.cxx \
+ src/playlist/plugins/AsxPlaylistPlugin.hxx \
+ src/playlist/plugins/RssPlaylistPlugin.cxx \
+ src/playlist/plugins/RssPlaylistPlugin.hxx
+endif
+
+if HAVE_GLIB
+libplaylist_plugins_a_SOURCES += \
+ src/playlist/plugins/PlsPlaylistPlugin.cxx \
+ src/playlist/plugins/PlsPlaylistPlugin.hxx
+endif
+
#
# Filter plugins
#
libfilter_plugins_a_SOURCES = \
- 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
+ src/AudioCompress/config.h \
+ src/AudioCompress/compress.h \
+ src/AudioCompress/compress.c \
+ src/filter/plugins/NullFilterPlugin.cxx \
+ src/filter/plugins/ChainFilterPlugin.cxx \
+ src/filter/plugins/ChainFilterPlugin.hxx \
+ src/filter/plugins/AutoConvertFilterPlugin.cxx \
+ src/filter/plugins/AutoConvertFilterPlugin.hxx \
+ src/filter/plugins/ConvertFilterPlugin.cxx \
+ src/filter/plugins/ConvertFilterPlugin.hxx \
+ src/filter/plugins/RouteFilterPlugin.cxx \
+ src/filter/plugins/NormalizeFilterPlugin.cxx \
+ src/filter/plugins/ReplayGainFilterPlugin.cxx \
+ src/filter/plugins/ReplayGainFilterPlugin.hxx \
+ src/filter/plugins/VolumeFilterPlugin.cxx \
+ src/filter/plugins/VolumeFilterPlugin.hxx
FILTER_LIBS = \
libfilter_plugins.a \
@@ -1032,29 +1459,10 @@ FILTER_LIBS = \
if HAVE_SYSTEMD
systemdsystemunit_DATA = \
- mpd.service
+ systemd/mpd.socket \
+ systemd/mpd.service
endif
-#
-# Sparse code analysis
-#
-# sparse is a semantic parser
-# URL: git://www.kernel.org/pub/scm/devel/sparse/sparse.git
-#
-
-SPARSE = sparse
-SPARSE_FLAGS =
-SPARSE_CPPFLAGS = $(DEFAULT_INCLUDES) \
- -I$(shell $(CC) -print-file-name=include) \
- -I$(shell $(CC) -print-file-name=include-fixed)
-SPARSE_CPPFLAGS += -D__SCHAR_MAX__=127 -D__SHRT_MAX__=32767 \
- -D__INT_MAX__=2147483647 -D__LONG_MAX__=2147483647
-SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES)))
-sparse-check:
- $(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(SPARSE_SRC)
-
-.PHONY: sparse-check
-
#
# Test programs
@@ -1065,12 +1473,20 @@ if ENABLE_TEST
C_TESTS = \
test/test_util \
test/test_byte_reverse \
+ test/test_rewind \
test/test_mixramp \
- test/test_icy_parser \
test/test_pcm \
test/test_protocol \
test/test_queue_priority
+if ENABLE_CURL
+C_TESTS += test/test_icy_parser
+endif
+
+if ENABLE_DATABASE
+C_TESTS += test/test_translate_song
+endif
+
if ENABLE_ARCHIVE
C_TESTS += test/test_archive
endif
@@ -1081,7 +1497,6 @@ noinst_PROGRAMS = \
$(C_TESTS) \
test/read_conf \
test/run_resolver \
- test/DumpDatabase \
test/run_input \
test/dump_text_file \
test/dump_playlist \
@@ -1093,6 +1508,15 @@ noinst_PROGRAMS = \
test/run_normalize \
test/software_volume
+if ENABLE_DATABASE
+noinst_PROGRAMS += test/DumpDatabase
+noinst_PROGRAMS += test/run_storage
+endif
+
+if ENABLE_NEIGHBOR_PLUGINS
+noinst_PROGRAMS += test/run_neighbor_explorer
+endif
+
if HAVE_AVAHI
noinst_PROGRAMS += test/run_avahi
endif
@@ -1112,12 +1536,12 @@ endif
test_read_conf_LDADD = \
libconf.a \
- libutil.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
+ libutil.a \
$(GLIB_LIBS)
test_read_conf_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/read_conf.cxx
test_run_resolver_LDADD = \
@@ -1125,30 +1549,53 @@ test_run_resolver_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_run_resolver_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/run_resolver.cxx
+if ENABLE_DATABASE
+
test_DumpDatabase_LDADD = \
$(DB_LIBS) \
$(TAG_LIBS) \
libconf.a \
libutil.a \
+ libevent.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
+ $(ICU_LDADD) \
$(GLIB_LIBS)
test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
src/protocol/Ack.cxx \
- src/Log.cxx \
- src/DatabaseError.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/Log.cxx src/LogBackend.cxx \
+ src/db/DatabaseError.cxx \
+ src/db/Registry.cxx \
+ src/db/Selection.cxx \
+ src/db/PlaylistVector.cxx \
+ src/db/DatabaseLock.cxx \
+ src/SongSave.cxx \
+ src/DetachedSong.cxx \
src/TagSave.cxx \
- src/SongFilter.cxx \
- src/TextFile.cxx
+ src/SongFilter.cxx
+
+if HAVE_LIBUPNP
+test_DumpDatabase_SOURCES += src/lib/expat/ExpatParser.cxx
+endif
+
+test_run_storage_LDADD = \
+ $(STORAGE_LIBS) \
+ $(FS_LIBS) \
+ libevent.a \
+ libthread.a \
+ libsystem.a \
+ libutil.a \
+ $(GLIB_LIBS)
+test_run_storage_SOURCES = \
+ src/Log.cxx src/LogBackend.cxx \
+ src/IOThread.cxx \
+ test/ScopeIOThread.hxx \
+ test/run_storage.cxx
+
+endif
test_run_input_LDADD = \
$(INPUT_LIBS) \
@@ -1158,15 +1605,47 @@ test_run_input_LDADD = \
libutil.a \
libevent.a \
libthread.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.cxx \
+ test/ScopeIOThread.hxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/TagSave.cxx
+if ENABLE_NEIGHBOR_PLUGINS
+
+test_run_neighbor_explorer_SOURCES = \
+ src/Log.cxx src/LogBackend.cxx \
+ src/IOThread.cxx \
+ test/run_neighbor_explorer.cxx
+test_run_neighbor_explorer_LDADD = $(AM_LDADD) \
+ $(GLIB_LIBS) \
+ $(NEIGHBOR_LIBS) \
+ $(INPUT_LIBS) \
+ $(ARCHIVE_LIBS) \
+ libtag.a \
+ libconf.a \
+ libevent.a \
+ $(FS_LIBS) \
+ libsystem.a \
+ libthread.a \
+ libutil.a
+
+if HAVE_LIBUPNP
+test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx
+endif
+
+if ENABLE_DESPOTIFY
+test_run_neighbor_explorer_SOURCES += \
+ src/lib/despotify/DespotifyUtils.cxx \
+ src/lib/despotify/DespotifyUtils.hxx
+endif
+
+endif
+
if ENABLE_ARCHIVE
test_visit_archive_LDADD = \
@@ -1177,18 +1656,34 @@ test_visit_archive_LDADD = \
libutil.a \
libevent.a \
libthread.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
$(GLIB_LIBS)
test_visit_archive_SOURCES = test/visit_archive.cxx \
- src/Log.cxx \
+ test/ScopeIOThread.hxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
- src/InputStream.cxx
+ src/input/Open.cxx
-if ENABLE_DESPOTIFY
-test_visit_archive_SOURCES += src/DespotifyUtils.cxx
endif
+if HAVE_ZLIB
+
+noinst_PROGRAMS += test/run_gzip test/run_gunzip
+
+test_run_gzip_LDADD = \
+ libutil.a \
+ $(FS_LIBS)
+test_run_gzip_SOURCES = test/run_gzip.cxx
+
+test_run_gunzip_SOURCES = test/run_gunzip.cxx \
+ src/Log.cxx src/LogBackend.cxx
+test_run_gunzip_LDADD = \
+ $(GLIB_LIBS) \
+ libutil.a \
+ $(FS_LIBS) \
+ libsystem.a
+
endif
test_dump_text_file_LDADD = \
@@ -1197,16 +1692,16 @@ test_dump_text_file_LDADD = \
$(TAG_LIBS) \
libconf.a \
libevent.a \
- libfs.a \
+ $(FS_LIBS) \
libsystem.a \
libthread.a \
libutil.a \
$(GLIB_LIBS)
test_dump_text_file_SOURCES = test/dump_text_file.cxx \
+ test/ScopeIOThread.hxx \
test/stdbin.h \
- src/Log.cxx \
- src/IOThread.cxx \
- src/TextInputStream.cxx
+ src/Log.cxx src/LogBackend.cxx \
+ src/IOThread.cxx
test_dump_playlist_LDADD = \
$(PLAYLIST_LIBS) \
@@ -1218,26 +1713,26 @@ test_dump_playlist_LDADD = \
libconf.a \
libevent.a \
libthread.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
libutil.a \
libpcm.a \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
- test/FakeDecoderAPI.cxx \
+ test/FakeDecoderAPI.cxx test/FakeDecoderAPI.hxx \
+ test/ScopeIOThread.hxx \
$(DECODER_SRC) \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
- src/Song.cxx src/TagSave.cxx \
+ src/TagSave.cxx \
src/TagFile.cxx \
- src/CheckAudioFormat.cxx \
- src/TextInputStream.cxx \
- src/cue/CueParser.cxx src/cue/CueParser.hxx
+ src/AudioFormat.cxx src/CheckAudioFormat.cxx \
+ src/DetachedSong.cxx
if HAVE_FLAC
test_dump_playlist_SOURCES += \
src/ReplayGainInfo.cxx \
- src/decoder/FlacMetadata.cxx
+ src/decoder/plugins/FlacMetadata.cxx
endif
test_run_decoder_LDADD = \
@@ -1249,13 +1744,15 @@ test_run_decoder_LDADD = \
libconf.a \
libevent.a \
libthread.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
libutil.a \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.cxx \
+ test/FakeDecoderAPI.cxx test/FakeDecoderAPI.hxx \
+ test/ScopeIOThread.hxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/ReplayGainInfo.cxx \
src/AudioFormat.cxx src/CheckAudioFormat.cxx \
@@ -1273,16 +1770,17 @@ test_read_tags_LDADD = \
libconf.a \
libevent.a \
libthread.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
libutil.a \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.cxx \
- test/FakeDecoderAPI.cxx \
- src/Log.cxx \
+ test/FakeDecoderAPI.cxx test/FakeDecoderAPI.hxx \
+ test/ScopeIOThread.hxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/ReplayGainInfo.cxx \
- src/CheckAudioFormat.cxx \
+ src/AudioFormat.cxx src/CheckAudioFormat.cxx \
$(DECODER_SRC)
if HAVE_ID3TAG
@@ -1291,41 +1789,32 @@ test_dump_rva2_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_dump_rva2_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/dump_rva2.cxx
endif
test_run_filter_LDADD = \
$(FILTER_LIBS) \
libconf.a \
- libutil.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
+ libutil.a \
$(GLIB_LIBS)
test_run_filter_SOURCES = test/run_filter.cxx \
test/FakeReplayGainConfig.cxx \
test/stdbin.h \
- src/Log.cxx \
- src/FilterPlugin.cxx src/FilterRegistry.cxx \
+ src/Log.cxx src/LogBackend.cxx \
+ src/filter/FilterPlugin.cxx src/filter/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/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
+ src/ReplayGainInfo.cxx
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
src/AudioParser.cxx
@@ -1335,8 +1824,8 @@ test_run_encoder_LDADD = \
libconf.a \
libpcm.a \
libthread.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
libutil.a \
$(GLIB_LIBS)
endif
@@ -1345,7 +1834,7 @@ if ENABLE_VORBIS_ENCODER
noinst_PROGRAMS += test/test_vorbis_encoder
test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
src/AudioParser.cxx \
@@ -1357,15 +1846,16 @@ test_test_vorbis_encoder_LDADD = $(MPD_LIBS) \
$(PCM_LIBS) \
$(TAG_LIBS) \
libconf.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
libutil.a \
$(GLIB_LIBS)
endif
test_software_volume_SOURCES = test/software_volume.cxx \
test/stdbin.h \
- src/CheckAudioFormat.cxx \
+ src/Log.cxx src/LogBackend.cxx \
+ src/AudioFormat.cxx src/CheckAudioFormat.cxx \
src/AudioParser.cxx
test_software_volume_LDADD = \
$(PCM_LIBS) \
@@ -1373,8 +1863,8 @@ test_software_volume_LDADD = \
$(GLIB_LIBS)
test_run_avahi_SOURCES = \
- src/Log.cxx \
- src/ZeroconfAvahi.cxx src/AvahiPoll.cxx \
+ src/Log.cxx src/LogBackend.cxx \
+ src/zeroconf/ZeroconfAvahi.cxx src/zeroconf/AvahiPoll.cxx \
test/ShutdownHandler.cxx test/ShutdownHandler.hxx \
test/run_avahi.cxx
test_run_avahi_CPPFLAGS = $(AM_CPPFLAGS) \
@@ -1382,20 +1872,21 @@ test_run_avahi_CPPFLAGS = $(AM_CPPFLAGS) \
test_run_avahi_LDADD = \
libevent.a \
libsystem.a \
+ libutil.a \
$(GLIB_LIBS) \
$(AVAHI_LIBS)
test_run_normalize_SOURCES = test/run_normalize.cxx \
test/stdbin.h \
src/CheckAudioFormat.cxx \
- src/AudioParser.cxx \
- src/AudioCompress/compress.c
+ src/AudioCompress/compress.c \
+ src/AudioParser.cxx
test_run_normalize_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_run_convert_SOURCES = test/run_convert.cxx \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/AudioFormat.cxx \
src/CheckAudioFormat.cxx \
src/AudioParser.cxx
@@ -1413,29 +1904,27 @@ test_run_output_LDADD = $(MPD_LIBS) \
$(TAG_LIBS) \
libconf.a \
libevent.a \
- libfs.a \
+ $(FS_LIBS) \
libsystem.a \
libthread.a \
libutil.a \
$(GLIB_LIBS)
test_run_output_SOURCES = test/run_output.cxx \
test/FakeReplayGainConfig.cxx \
+ test/ScopeIOThread.hxx \
test/stdbin.h \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
src/IOThread.cxx \
src/CheckAudioFormat.cxx \
src/AudioFormat.cxx \
src/AudioParser.cxx \
- src/Timer.cxx \
- src/Page.cxx \
- src/OutputError.cxx \
- 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/output/Domain.cxx \
+ src/output/Init.cxx src/output/Finish.cxx src/output/Registry.cxx \
+ src/output/OutputPlugin.cxx \
+ src/mixer/MixerControl.cxx \
+ src/mixer/MixerType.cxx \
+ src/filter/FilterPlugin.cxx \
+ src/filter/FilterConfig.cxx \
src/ReplayGainInfo.cxx
test_read_mixer_LDADD = \
@@ -1443,16 +1932,17 @@ test_read_mixer_LDADD = \
libmixer_plugins.a \
$(OUTPUT_LIBS) \
libconf.a \
- libutil.a \
libevent.a \
+ $(FS_LIBS) \
libsystem.a \
- libfs.a \
+ libutil.a \
$(GLIB_LIBS)
test_read_mixer_SOURCES = test/read_mixer.cxx \
- src/Log.cxx \
- src/MixerControl.cxx \
- src/FilterPlugin.cxx \
- src/filter/VolumeFilterPlugin.cxx
+ src/Log.cxx src/LogBackend.cxx \
+ src/mixer/MixerControl.cxx \
+ src/filter/FilterPlugin.cxx \
+ src/AudioFormat.cxx \
+ src/filter/plugins/VolumeFilterPlugin.cxx
if ENABLE_BZIP2_TEST
TESTS += test/test_archive_bzip2.sh
@@ -1470,9 +1960,9 @@ if ENABLE_INOTIFY
noinst_PROGRAMS += test/run_inotify
test_run_inotify_SOURCES = test/run_inotify.cxx \
test/ShutdownHandler.cxx test/ShutdownHandler.hxx \
- src/Log.cxx \
- src/InotifyDomain.cxx \
- src/InotifySource.cxx
+ src/Log.cxx src/LogBackend.cxx \
+ src/db/update/InotifyDomain.cxx \
+ src/db/update/InotifySource.cxx
test_run_inotify_LDADD = \
libevent.a \
libsystem.a \
@@ -1481,6 +1971,7 @@ test_run_inotify_LDADD = \
endif
test_test_util_SOURCES = \
+ test/TestCircularBuffer.hxx \
test/test_util.cxx
test_test_util_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_util_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
@@ -1496,26 +1987,44 @@ test_test_byte_reverse_LDADD = \
libutil.a \
$(CPPUNIT_LIBS)
+test_test_rewind_SOURCES = \
+ src/Log.cxx src/LogBackend.cxx \
+ test/test_rewind.cxx
+test_test_rewind_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
+test_test_rewind_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
+test_test_rewind_LDADD = \
+ $(GLIB_LIBS) \
+ $(INPUT_LIBS) \
+ libthread.a \
+ libtag.a \
+ libutil.a \
+ $(CPPUNIT_LIBS)
+
test_test_mixramp_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/test_mixramp.cxx
test_test_mixramp_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_mixramp_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
test_test_mixramp_LDADD = \
+ libutil.a \
$(GLIB_LIBS) \
$(CPPUNIT_LIBS)
+if ENABLE_CURL
test_test_icy_parser_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/test_icy_parser.cxx
test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
test_test_icy_parser_LDADD = \
libtag.a \
+ libutil.a \
$(GLIB_LIBS) \
$(CPPUNIT_LIBS)
+endif
test_test_pcm_SOURCES = \
+ src/AudioFormat.cxx \
test/test_pcm_util.hxx \
test/test_pcm_dither.cxx \
test/test_pcm_pack.cxx \
@@ -1523,6 +2032,7 @@ test_test_pcm_SOURCES = \
test/test_pcm_format.cxx \
test/test_pcm_volume.cxx \
test/test_pcm_mix.cxx \
+ test/test_pcm_export.cxx \
test/test_pcm_all.hxx \
test/test_pcm_main.cxx
test_test_pcm_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
@@ -1534,15 +2044,38 @@ test_test_pcm_LDADD = \
$(GLIB_LIBS)
test_test_archive_SOURCES = \
- src/Log.cxx \
+ src/Log.cxx src/LogBackend.cxx \
test/test_archive.cxx
test_test_archive_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_archive_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
test_test_archive_LDADD = \
libarchive.a \
+ libutil.a \
+ $(GLIB_LIBS) \
+ $(CPPUNIT_LIBS)
+
+if ENABLE_DATABASE
+
+test_test_translate_song_SOURCES = \
+ src/playlist/PlaylistSong.cxx \
+ src/PlaylistError.cxx \
+ src/DetachedSong.cxx \
+ src/SongLoader.cxx \
+ src/Log.cxx \
+ test/test_translate_song.cxx
+test_test_translate_song_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
+test_test_translate_song_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
+test_test_translate_song_LDADD = \
+ $(STORAGE_LIBS) \
+ libtag.a \
+ $(FS_LIBS) \
+ libsystem.a \
+ libutil.a \
$(GLIB_LIBS) \
$(CPPUNIT_LIBS)
+endif
+
test_test_protocol_SOURCES = \
src/protocol/ArgParser.cxx \
test/test_protocol.cxx
@@ -1554,7 +2087,8 @@ test_test_protocol_LDADD = \
$(CPPUNIT_LIBS)
test_test_queue_priority_SOURCES = \
- src/Queue.cxx \
+ src/queue/Queue.cxx \
+ src/DetachedSong.cxx \
test/test_queue_priority.cxx
test_test_queue_priority_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
test_test_queue_priority_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
@@ -1585,7 +2119,7 @@ endif
#
man_MANS = doc/mpd.1 doc/mpd.conf.5
-doc_DATA = AUTHORS COPYING NEWS README UPGRADING doc/mpdconf.example
+doc_DATA = AUTHORS COPYING NEWS README doc/mpdconf.example
DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml
@@ -1599,21 +2133,13 @@ user_DATA = $(wildcard doc/user/*.html)
developerdir = $(docdir)/developer
developer_DATA = $(wildcard doc/developer/*.html)
-if HAVE_XMLTO
-
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
$(DOCBOOK_HTML): %/index.html: %.xml
- $(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html $<
-
-else
-
-DOCBOOK_HTML =
-
-endif
+ $(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html --stringparam use.id.as.filename=1 $<
doc/api/html/index.html: doc/doxygen.conf
- @mkdir -p $(@D)
+ @$(MKDIR_P) $(@D)
$(DOXYGEN) $<
all-local: $(DOCBOOK_HTML) doc/api/html/index.html
@@ -1652,4 +2178,12 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
test/test_archive_zzip.sh \
$(wildcard scripts/*.sh) \
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
+ systemd/mpd.socket \
+ android/AndroidManifest.xml \
+ android/build.py \
+ android/custom_rules.xml \
+ android/res/values/strings.xml \
+ android/src/Bridge.java \
+ android/src/Loader.java \
+ android/src/Main.java \
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico
diff --git a/NEWS b/NEWS
index df37e6ceb..14676a5a7 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,174 @@
+ver 0.19.8 (not yet released)
+* input
+ - mms: reduce delay at the beginning of playback
+* decoder
+ - dsdiff, dsf: allow ID3 tags larger than 4 kB
+ - ffmpeg: support interleaved floating point
+* fix clang 3.6 warnings
+
+ver 0.19.7 (2014/12/17)
+* input
+ - nfs: fix crash while canceling a failing file open operation
+ - nfs: fix memory leak on connection failure
+ - nfs: fix reconnect after mount failure
+ - nfs: implement mount timeout (60 seconds)
+* storage
+ - nfs: implement I/O timeout (60 seconds)
+* playlist
+ - embcue: fix filename suffix detection
+ - don't skip non-existent songs in "listplaylist"
+* decoder
+ - ffmpeg: fix time stamp underflow
+* fix memory allocator bug on Windows
+
+ver 0.19.6 (2014/12/08)
+* decoder
+ - ffmpeg: support FFmpeg 2.5
+* fix build failure with musl
+* android
+ - update libFLAC to 1.3.1
+ - update FFmpeg to 2.5
+
+ver 0.19.5 (2014/11/26)
+* input
+ - nfs: fix crash on connection failure
+* archive
+ - zzip: fix crash after seeking
+* decoder
+ - dsdiff, dsf, opus: fix deadlock while seeking
+ - mp4v2: remove because of incompatible license
+
+ver 0.19.4 (2014/11/18)
+* protocol
+ - workaround for buggy clients that send "add /"
+* decoder
+ - ffmpeg: support opus
+ - opus: add MIME types audio/ogg and application/ogg
+* fix crash on failed filename charset conversion
+* fix local socket detection from uid=0 (root)
+
+ver 0.19.3 (2014/11/11)
+* protocol
+ - fix "(null)" result string to "list" when AlbumArtist is disabled
+* database
+ - upnp: fix breakage due to malformed URIs
+* input
+ - curl: another fix for redirected streams
+* decoder
+ - audiofile: fix crash while playing streams
+ - audiofile: fix bit rate calculation
+ - ffmpeg: support opus
+ - opus: fix bogus duration on streams
+ - opus: support chained streams
+ - opus: improved error logging
+* fix distorted audio with soxr resampler
+* fix build failure on Mac OS X with non-Apple compilers
+
+ver 0.19.2 (2014/11/02)
+* input
+ - curl: fix redirected streams
+* playlist
+ - don't allow empty playlist name
+ - m3u: don't ignore unterminated last line
+ - m3u: recognize the file suffix ".m3u8"
+* decoder
+ - ignore URI query string for plugin detection
+ - faad: remove workaround for ancient libfaad2 ABI bug
+ - ffmpeg: recognize MIME type audio/aacp
+ - mad: fix negative replay gain values
+* output
+ - fix memory leak after filter initialization error
+ - fall back to PCM if given DSD sample rate is not supported
+* fix assertion failure on unsupported PCM conversion
+* auto-disable plugins that require GLib when --disable-glib is used
+
+ver 0.19.1 (2014/10/19)
+* input
+ - mms: fix deadlock bug
+* playlist
+ - extm3u: fix Extended M3U detection
+ - m3u, extm3u, cue: fix truncated lines
+* fix build failure on Mac OS X
+* add missing file systemd/mpd.socket to tarball
+
+ver 0.19 (2014/10/10)
+* protocol
+ - new commands "addtagid", "cleartagid", "listfiles", "listmounts",
+ "listneighbors", "mount", "rangeid", "unmount"
+ - "lsinfo" and "readcomments" allowed for remote files
+ - "listneighbors" lists file servers on the local network
+ - "playlistadd" supports file:///
+ - "idle" with unrecognized event name fails
+ - "list" on album artist falls back to the artist tag
+ - "list" and "count" allow grouping
+ - new "search"/"find" filter "modified-since"
+ - "seek*" allows fractional position
+ - close connection after syntax error
+* database
+ - proxy: forward "idle" events
+ - proxy: forward the "update" command
+ - proxy: copy "Last-Modified" from remote directories
+ - simple: compress the database file using gzip
+ - upnp: new plugin
+ - cancel the update on shutdown
+* storage
+ - music_directory can point to a remote file server
+ - nfs: new plugin
+ - smbclient: new plugin
+* playlist
+ - cue: fix bogus duration of the last track
+ - cue: restore CUE tracks from state file
+ - soundcloud: use https instead of http
+ - soundcloud: add default API key
+* archive
+ - read tags from songs in an archive
+* input
+ - alsa: new input plugin
+ - curl: options "verify_peer" and "verify_host"
+ - ffmpeg: update offset after seeking
+ - ffmpeg: improved error messages
+ - mms: non-blocking I/O
+ - nfs: new input plugin
+ - smbclient: new input plugin
+* filter
+ - volume: improved software volume dithering
+* decoder:
+ - vorbis, flac, opus: honor DESCRIPTION= tag in Xiph-based files as a comment to the song
+ - audiofile: support scanning remote files
+ - audiofile: log libaudiofile errors
+ - dsdiff, dsf: report bit rate
+ - dsdiff, dsf: implement seeking
+ - dsf: support DSD512
+ - dsf: support multi-channel files
+ - dsf: fix big-endian bugs
+ - dsf: fix noise at end of malformed file
+ - mpg123: support ID3v2, ReplayGain and MixRamp
+ - sndfile: support scanning remote files
+ - sndfile: support tags "comment", "album", "track", "genre"
+ - sndfile: native floating point playback
+ - sndfile: optimized 16 bit playback
+ - mp4v2: support playback of MP4 files.
+* encoder:
+ - shine: new encoder plugin
+* output
+ - alsa: support native DSD playback
+ - alsa: rename "DSD over USB" to "DoP"
+ - osx: fix hang after (un)plugging headphones
+* threads:
+ - the update thread runs at "idle" priority
+ - the output thread runs at "real-time" priority
+ - increase kernel timer slack on Linux
+ - name each thread (for debugging)
+* configuration
+ - allow playlist directory without music directory
+ - use XDG to auto-detect "music_directory" and "db_file"
+* add tags "AlbumSort", "MUSICBRAINZ_RELEASETRACKID"
+* disable global Latin-1 fallback for tag values
+* new resampler option using libsoxr
+* ARM NEON optimizations
+* install systemd unit for socket activation
+* Android port
+
ver 0.18.22 (not yet released)
* fix clang 3.6 warnings
@@ -51,6 +222,7 @@ ver 0.18.14 (2014/09/11)
ver 0.18.13 (2014/08/31)
* protocol
- don't change song on "seekcur" in random mode
+
* decoder
- dsdiff, dsf: fix endless loop on malformed file
- ffmpeg: support ffmpeg/libav version 11
diff --git a/UPGRADING b/UPGRADING
deleted file mode 100644
index e838371da..000000000
--- a/UPGRADING
+++ /dev/null
@@ -1,92 +0,0 @@
- Music Player Daemon (MPD) - UPGRADING
-
-Upgrading to 0.14
------------------
-
-The filesystem character set is determined by GLib, if it is not
-configured. GLib has an affinity towards UTF-8, while older MPD
-versions used to choose ISO-Latin-1.
-
-
-Upgrading to 0.13.0
--------------------
-
-JACK, Avahi, and libsamplerate have been added as optional dependencies.
-FLAC/OggFLAC now supports the 1.1.3 API, and libmikmod 3.2.0 betas are
-supported as well.
-
-New mpd.conf parameters include zeroconf_name, samplerate_converter, and
-gapless_mp3_playback. See the mpd.conf man page or updated mpconf.example for
-more information on these parameters.
-
-Support for the ID3v2 "Original Artist/Performer" tag has been added. Your
-MP3s will need to be rescanned for these tags to be included in the database.
-This can be done by running mpd --create-db.
-
-Upgrading to 0.12.0
--------------------
-
-The ao_driver and ao_driver_options config parameters have been removed and
-replaced with the audio_output config section. You will have to update your
-config file to use this instead. See the mpd.conf man page or the new
-mpdconf.example for details on specifying audio_output sections.
-
-The db_file parameter is no longer optional. If you did not specify it in your
-old config file then you will have to add it in order to run 0.12.0.
-
-Support for OggFLAC and Musepack audio files has been added. Additionally,
-scanning of MP3 files has been improved. To make use of these updates it is
-highly recommended that you run mpd --create-db to recreate your entire
-database.
-
-Upgrading to 0.11.0
--------------------
-
-The database format has changed a little bit, but in a backward compatible way.
-This means that if you upgrade to 0.11.0 from 0.10.x, you do not need to make
-any changes. However, if you downgrade back to 0.10.x, then you will need
-to recreate your db.
-
-The default port for MPD is now 6600, so update your mpd and client
-configurations appropriately.
-
-Upgrading to 0.10.0
--------------------
-
-All information is now stored in the db in UTF-8 format, and the character
-set used for the filesystem is stored in the db. Thus, it is highly
-recommended that you recreate the db. To do so, run mpd with the
-"--create-db" command line option. Also, note that the filesystem
-character set will be determined from your current locale settings.
-If your locale settings are not the same as those used for the filesystem,
-then use the config file parameter "filesystem_charset" to specify the
-correct character set (this maybe necessary if you create the db with root).
-
-Upgrading to 0.9.3
-------------------
-
-Wave support was added, so to have your wave files added, update the db (mpc
-update).
-
-Also, song lengths are now stored in the db. To get this stuff
-added to the db, you will need to recreate the db from scratch. To do this,
-run mpd with the "--create-db" commandline option.
-
-Upgrading to 0.9.0
-------------------
-
-The "stop_on_error" config parameter was removed, so be sure to remove this
-parameter from your config file.
-
-Upgrading to 0.8.x
-------------------
-
-If you have FLACs, then to have them added to your list of available music,
-just use "update".
-
-Upgrading from 0.5.x to 0.6.x
------------------------------
-If you have not compiled MPD with "make ogg", then nothing is needed.
-
-If you compiled with "make ogg", just use "update" (available via the phpMp
-interface) to add your OGGs to MPD's list of available music.
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
new file mode 100644
index 000000000..a1e045e26
--- /dev/null
+++ b/android/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.musicpd"
+ android:installLocation="auto"
+ android:versionCode="12"
+ android:versionName="0.19.8">
+
+ <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
+
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".Main"
+ android:label="@string/app_name"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/android/build.py b/android/build.py
new file mode 100755
index 000000000..8455dd6a9
--- /dev/null
+++ b/android/build.py
@@ -0,0 +1,433 @@
+#!/usr/bin/env python3
+
+import os, os.path
+import sys, shutil, subprocess
+import urllib.request
+import hashlib
+import re
+
+if len(sys.argv) < 3:
+ print("Usage: build.py SDK_PATH NDK_PATH [configure_args...]", file=sys.stderr)
+ sys.exit(1)
+
+sdk_path = sys.argv[1]
+ndk_path = sys.argv[2]
+configure_args = sys.argv[3:]
+
+if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
+ print("SDK not found in", ndk_path, file=sys.stderr)
+ sys.exit(1)
+
+if not os.path.isdir(ndk_path):
+ print("NDK not found in", ndk_path, file=sys.stderr)
+ sys.exit(1)
+
+# the path to the MPD sources
+mpd_path = os.path.dirname(os.path.dirname(sys.argv[0]))
+
+# output directories
+lib_path = os.path.abspath('lib')
+tarball_path = lib_path
+src_path = os.path.join(lib_path, 'src')
+build_path = os.path.join(lib_path, 'build')
+root_path = os.path.join(lib_path, 'root')
+
+# build host configuration
+build_arch = 'linux-x86_64'
+
+# redirect pkg-config to use our root directory instead of the default
+# one on the build host
+os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
+
+# select the NDK compiler
+gcc_version = '4.9'
+llvm_version = '3.5'
+
+# select the NDK target
+ndk_arch = 'arm'
+host_arch = 'arm-linux-androideabi'
+android_abi = 'armeabi-v7a'
+ndk_platform = 'android-14'
+
+# set up the NDK toolchain
+
+gcc_toolchain = os.path.join(ndk_path, 'toolchains', host_arch + '-' + gcc_version, 'prebuilt', build_arch)
+llvm_toolchain = os.path.join(ndk_path, 'toolchains', 'llvm-' + llvm_version, 'prebuilt', build_arch)
+ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
+target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
+
+llvm_triple = 'armv7-none-linux-androideabi'
+
+def select_toolchain(use_cxx, use_clang):
+ global cc, cxx, ar, strip, cflags, cxxflags, cppflags, ldflags, libs
+
+ target_arch = '-march=armv7-a -mfloat-abi=softfp'
+ if use_clang:
+ cc = os.path.join(llvm_toolchain, 'bin/clang')
+ cxx = os.path.join(llvm_toolchain, 'bin/clang++')
+ target_arch += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + gcc_toolchain
+ else:
+ cc = os.path.join(gcc_toolchain, 'bin', host_arch + '-gcc')
+ cxx = os.path.join(gcc_toolchain, 'bin', host_arch + '-g++')
+ ar = os.path.join(gcc_toolchain, 'bin', host_arch + '-ar')
+ strip = os.path.join(gcc_toolchain, 'bin', host_arch + '-strip')
+
+ libstdcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/gnu-libstdc++', gcc_version)
+ libstdcxx_cppflags = '-isystem ' + os.path.join(libstdcxx_path, 'include') + ' -isystem ' + os.path.join(libstdcxx_path, 'libs', android_abi, 'include')
+ if use_clang:
+ libstdcxx_cppflags += ' -D__STRICT_ANSI__'
+ libstdcxx_ldadd = os.path.join(libstdcxx_path, 'libs', android_abi, 'libgnustl_static.a')
+
+ cflags = '-Os -g ' + target_arch
+ cxxflags = '-Os -g ' + target_arch
+ cppflags = '--sysroot=' + target_root + ' -I' + root_path + '/include'
+ ldflags = '--sysroot=' + target_root + ' -L' + root_path + '/lib'
+ libs = ''
+
+ if use_cxx:
+ libs += ' ' + libstdcxx_ldadd
+ cppflags += ' ' + libstdcxx_cppflags
+
+def file_md5(path):
+ """Calculate the MD5 checksum of a file and return it in hexadecimal notation."""
+
+ with open(path, 'rb') as f:
+ m = hashlib.md5()
+ while True:
+ data = f.read(65536)
+ if len(data) == 0:
+ # end of file
+ return m.hexdigest()
+ m.update(data)
+
+def download_tarball(url, md5):
+ """Download a tarball, verify its MD5 checksum and return the local path."""
+
+ global tarball_path
+ os.makedirs(tarball_path, exist_ok=True)
+ path = os.path.join(tarball_path, os.path.basename(url))
+
+ try:
+ calculated_md5 = file_md5(path)
+ if md5 == calculated_md5: return path
+ os.unlink(path)
+ except FileNotFoundError:
+ pass
+
+ tmp_path = path + '.tmp'
+
+ print("download", url)
+ urllib.request.urlretrieve(url, tmp_path)
+ calculated_md5 = file_md5(tmp_path)
+ if calculated_md5 != md5:
+ os.unlink(tmp_path)
+ raise "MD5 mismatch"
+
+ os.rename(tmp_path, path)
+ return path
+
+class Project:
+ def __init__(self, url, md5, installed, name=None, version=None,
+ base=None,
+ use_cxx=False, use_clang=False):
+ if base is None:
+ basename = os.path.basename(url)
+ m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
+ if not m: raise
+ self.base = m.group(1)
+ else:
+ self.base = base
+
+ if name is None or version is None:
+ m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
+ if name is None: name = m.group(1)
+ if version is None: version = m.group(2)
+
+ self.name = name
+ self.version = version
+
+ self.url = url
+ self.md5 = md5
+ self.installed = installed
+
+ self.use_cxx = use_cxx
+ self.use_clang = use_clang
+
+ def download(self):
+ return download_tarball(self.url, self.md5)
+
+ def is_installed(self):
+ global root_path
+ tarball = self.download()
+ installed = os.path.join(root_path, self.installed)
+ tarball_mtime = os.path.getmtime(tarball)
+ try:
+ return os.path.getmtime(installed) >= tarball_mtime
+ except FileNotFoundError:
+ return False
+
+ def unpack(self):
+ global src_path
+ tarball = self.download()
+ path = os.path.join(src_path, self.base)
+ try:
+ shutil.rmtree(path)
+ except FileNotFoundError:
+ pass
+ os.makedirs(src_path, exist_ok=True)
+ subprocess.check_call(['/bin/tar', 'xfC', tarball, src_path])
+ return path
+
+ def make_build_path(self):
+ path = os.path.join(build_path, self.base)
+ try:
+ shutil.rmtree(path)
+ except FileNotFoundError:
+ pass
+ os.makedirs(path, exist_ok=True)
+ return path
+
+class AutotoolsProject(Project):
+ def __init__(self, url, md5, installed, configure_args=[],
+ autogen=False,
+ cppflags='',
+ **kwargs):
+ Project.__init__(self, url, md5, installed, **kwargs)
+ self.configure_args = configure_args
+ self.autogen = autogen
+ self.cppflags = cppflags
+
+ def build(self):
+ src = self.unpack()
+ if self.autogen:
+ subprocess.check_call(['/usr/bin/aclocal'], cwd=src)
+ subprocess.check_call(['/usr/bin/automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
+ subprocess.check_call(['/usr/bin/autoconf'], cwd=src)
+ subprocess.check_call(['/usr/bin/libtoolize', '--force'], cwd=src)
+
+ build = self.make_build_path()
+
+ select_toolchain(use_cxx=self.use_cxx, use_clang=self.use_clang)
+ configure = [
+ os.path.join(src, 'configure'),
+ 'CC=' + cc,
+ 'CXX=' + cxx,
+ 'CFLAGS=' + cflags,
+ 'CXXFLAGS=' + cxxflags,
+ 'CPPFLAGS=' + cppflags + ' ' + self.cppflags,
+ 'LDFLAGS=' + ldflags,
+ 'LIBS=' + libs,
+ 'AR=' + ar,
+ 'STRIP=' + strip,
+ '--host=' + host_arch,
+ '--prefix=' + root_path,
+ '--with-sysroot=' + target_root,
+ '--enable-silent-rules',
+ ] + self.configure_args
+
+ subprocess.check_call(configure, cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'], cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', 'install'], cwd=build)
+
+class FfmpegProject(Project):
+ def __init__(self, url, md5, installed, configure_args=[],
+ cppflags='',
+ **kwargs):
+ Project.__init__(self, url, md5, installed, **kwargs)
+ self.configure_args = configure_args
+ self.cppflags = cppflags
+
+ def build(self):
+ src = self.unpack()
+ build = self.make_build_path()
+
+ select_toolchain(use_cxx=self.use_cxx, use_clang=self.use_clang)
+ configure = [
+ os.path.join(src, 'configure'),
+ '--cc=' + cc,
+ '--cxx=' + cxx,
+ '--extra-cflags=' + cflags + ' ' + cppflags + ' ' + self.cppflags,
+ '--extra-cxxflags=' + cxxflags + ' ' + cppflags + ' ' + self.cppflags,
+ '--extra-ldflags=' + ldflags,
+ '--extra-libs=' + libs,
+ '--ar=' + ar,
+ '--enable-cross-compile',
+ '--target-os=linux',
+ '--arch=' + ndk_arch,
+ '--cpu=cortex-a8',
+ '--prefix=' + root_path,
+ ] + self.configure_args
+
+ subprocess.check_call(configure, cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'], cwd=build)
+ subprocess.check_call(['/usr/bin/make', '--quiet', 'install'], cwd=build)
+
+class BoostProject(Project):
+ def __init__(self, url, md5, installed,
+ **kwargs):
+ m = re.match(r'.*/boost_(\d+)_(\d+)_(\d+)\.tar\.bz2$', url)
+ version = "%s.%s.%s" % (m.group(1), m.group(2), m.group(3))
+ Project.__init__(self, url, md5, installed,
+ name='boost', version=version,
+ **kwargs)
+
+ def build(self):
+ src = self.unpack()
+
+ # install the headers manually; don't build any library
+ # (because right now, we only use header-only libraries)
+ includedir = os.path.join(root_path, 'include')
+ for dirpath, dirnames, filenames in os.walk(os.path.join(src, 'boost')):
+ relpath = dirpath[len(src)+1:]
+ destdir = os.path.join(includedir, relpath)
+ try:
+ os.mkdir(destdir)
+ except:
+ pass
+ for name in filenames:
+ if name[-4:] == '.hpp':
+ shutil.copyfile(os.path.join(dirpath, name),
+ os.path.join(destdir, name))
+
+# a list of third-party libraries to be used by MPD on Android
+thirdparty_libs = [
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz',
+ '5c3a34309d8b98640827e5d0991a4015',
+ 'lib/libogg.a',
+ ['--disable-shared', '--enable-static'],
+ ),
+
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.4.tar.xz',
+ '55f2288055e44754275a17c9a2497391',
+ 'lib/libvorbis.a',
+ ['--disable-shared', '--enable-static'],
+ ),
+
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz',
+ 'c5a8cf7c0b066759542bc4ca46817ac6',
+ 'lib/libopus.a',
+ ['--disable-shared', '--enable-static'],
+ use_clang=True,
+ ),
+
+ AutotoolsProject(
+ 'http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz',
+ 'b9922c9a0378c88d3e901b234f852698',
+ 'lib/libFLAC.a',
+ [
+ '--disable-shared', '--enable-static',
+ '--disable-xmms-plugin', '--disable-cpplibs',
+ ],
+ use_clang=True,
+ ),
+
+ AutotoolsProject(
+ 'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.1b.tar.gz',
+ 'e5808ad997ba32c498803822078748c3',
+ 'lib/libid3tag.a',
+ ['--disable-shared', '--enable-static'],
+ autogen=True,
+ ),
+
+ AutotoolsProject(
+ 'ftp://ftp.mars.org/pub/mpeg/libmad-0.15.1b.tar.gz',
+ '1be543bc30c56fb6bea1d7bf6a64e66c',
+ 'lib/libmad.a',
+ ['--disable-shared', '--enable-static'],
+ autogen=True,
+ ),
+
+ FfmpegProject(
+ 'http://ffmpeg.org/releases/ffmpeg-2.5.tar.bz2',
+ '4346fe710cc6bdd981f6534d2420d1ab',
+ 'lib/libavcodec.a',
+ [
+ '--disable-shared', '--enable-static',
+ '--enable-gpl',
+ '--enable-small',
+ '--disable-pthreads',
+ '--disable-runtime-cpudetect',
+ '--disable-programs',
+ '--disable-doc',
+ '--disable-avdevice',
+ '--disable-swresample',
+ '--disable-swscale',
+ '--disable-postproc',
+ '--disable-avfilter',
+ '--disable-network',
+ '--disable-encoders',
+ '--disable-protocols',
+ '--disable-outdevs',
+ '--disable-filters',
+ ],
+ ),
+
+ AutotoolsProject(
+ 'http://curl.haxx.se/download/curl-7.39.0.tar.lzma',
+ 'e9aa6dec29920eba8ef706ea5823bad7',
+ 'lib/libcurl.a',
+ [
+ '--disable-shared', '--enable-static',
+ '--disable-debug',
+ '--enable-http',
+ '--enable-ipv6',
+ '--disable-ftp', '--disable-file',
+ '--disable-ldap', '--disable-ldaps',
+ '--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
+ '--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
+ '--disable-gopher',
+ '--disable-manual',
+ '--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
+ '--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
+ '--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
+ ],
+ use_clang=True,
+ ),
+
+ BoostProject(
+ 'http://netcologne.dl.sourceforge.net/project/boost/boost/1.55.0/boost_1_55_0.tar.bz2',
+ 'd6eef4b4cacb2183f2bf265a5a03a354',
+ 'include/boost/version.hpp',
+ ),
+]
+
+# build the third-party libraries
+for x in thirdparty_libs:
+ if not x.is_installed():
+ x.build()
+
+# configure and build MPD
+select_toolchain(use_cxx=True, use_clang=True)
+
+configure = [
+ os.path.join(mpd_path, 'configure'),
+ 'CC=' + cc,
+ 'CXX=' + cxx,
+ 'CFLAGS=' + cflags,
+ 'CXXFLAGS=' + cxxflags,
+ 'CPPFLAGS=' + cppflags,
+ 'LDFLAGS=' + ldflags,
+ 'LIBS=' + libs,
+ 'AR=' + ar,
+ 'STRIP=' + strip,
+ '--host=' + host_arch,
+ '--prefix=' + root_path,
+ '--with-sysroot=' + target_root,
+ '--with-android-sdk=' + sdk_path,
+
+ '--enable-silent-rules',
+
+ '--disable-glib',
+ '--disable-icu',
+
+ # disabled for now because these features require GLib:
+ '--disable-httpd-output',
+ '--disable-vorbis-encoder',
+
+] + configure_args
+
+subprocess.check_call(configure)
+subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'])
diff --git a/android/custom_rules.xml b/android/custom_rules.xml
new file mode 100644
index 000000000..68db46590
--- /dev/null
+++ b/android/custom_rules.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project name="mpd_rules">
+ <!-- setting these two properties works around a bug in Android
+ SDK's build.xml, which deletes all .class files every time -->
+ <property name="build.last.is.packaging.debug" value="true" />
+ <property name="build.is.packaging.debug" value="true" />
+
+ <target name="compile-jni-classes"
+ depends="-set-debug-mode,-compile"/>
+</project>
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
new file mode 100644
index 000000000..416c8de9f
--- /dev/null
+++ b/android/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+ <string name="app_name">MPD</string>
+</resources>
diff --git a/android/src/Bridge.java b/android/src/Bridge.java
new file mode 100644
index 000000000..537343b56
--- /dev/null
+++ b/android/src/Bridge.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package org.musicpd;
+
+import android.content.Context;
+
+/**
+ * Bridge to native code.
+ */
+public class Bridge {
+ public static native void run(Context context);
+ public static native void shutdown();
+}
diff --git a/android/src/Loader.java b/android/src/Loader.java
new file mode 100644
index 000000000..fdaa27753
--- /dev/null
+++ b/android/src/Loader.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package org.musicpd;
+
+import android.util.Log;
+
+public class Loader {
+ private static final String TAG = "MPD";
+
+ public static boolean loaded = false;
+ public static String error;
+
+ static {
+ try {
+ System.loadLibrary("mpd");
+ loaded = true;
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, e.getMessage());
+ error = e.getMessage();
+ }
+ }
+}
diff --git a/android/src/Main.java b/android/src/Main.java
new file mode 100644
index 000000000..d87f7709b
--- /dev/null
+++ b/android/src/Main.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package org.musicpd;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.widget.TextView;
+import android.util.Log;
+
+public class Main extends Activity implements Runnable {
+ private static final String TAG = "MPD";
+
+ Thread thread;
+
+ TextView textView;
+
+ final Handler quitHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ textView.setText("Music Player Daemon has quit");
+
+ // TODO: what now? restart?
+ }
+ };
+
+ @Override protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!Loader.loaded) {
+ TextView tv = new TextView(this);
+ tv.setText("Failed to load the native MPD libary.\n" +
+ "Report this problem to us, and include the following information:\n" +
+ "ABI=" + Build.CPU_ABI + "\n" +
+ "PRODUCT=" + Build.PRODUCT + "\n" +
+ "FINGERPRINT=" + Build.FINGERPRINT + "\n" +
+ "error=" + Loader.error);
+ setContentView(tv);
+ return;
+ }
+
+ if (thread == null || !thread.isAlive()) {
+ thread = new Thread(this, "NativeMain");
+ thread.start();
+ }
+
+ textView = new TextView(this);
+ textView.setText("Music Player Daemon is running"
+ + "\nCAUTION: this version is EXPERIMENTAL!");
+ setContentView(textView);
+ }
+
+ @Override public void run() {
+ Bridge.run(this);
+ quitHandler.sendMessage(quitHandler.obtainMessage());
+ }
+}
diff --git a/autogen.sh b/autogen.sh
index f163e35a7..8a1ac398b 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,137 +1,11 @@
#!/bin/sh
-# Run this to set up the build system: configure, makefiles, etc.
-# (at one point this was based on the version in enlightenment's cvs)
-package="mpd"
+set -e
-olddir="`pwd`"
-srcdir="`dirname $0`"
-test -z "$srcdir" && srcdir=.
-cd "$srcdir"
-DIE=
-AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]*\).*/\1/"
-AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/"
-VERSIONMKINT="sed -e s/[^0-9]//"
-if test -n "$AM_FORCE_VERSION"
-then
- AM_VERSIONS="$AM_FORCE_VERSION"
-else
- AM_VERSIONS='1.11'
-fi
-if test -n "$AC_FORCE_VERSION"
-then
- AC_VERSIONS="$AC_FORCE_VERSION"
-else
- AC_VERSIONS='2.60 2.61'
-fi
+rm -rf config.cache build
+mkdir build
-versioned_bins ()
-{
- bin="$1"
- needed_int=`echo $VERNEEDED | $VERSIONMKINT`
- for i in $VERSIONS
- do
- i_int=`echo $i | $VERSIONMKINT`
- if test $i_int -ge $needed_int
- then
- echo $bin-$i $bin$i $bin-$i_int $bin$i_int
- fi
- done
- echo $bin
-}
-
-for c in autoconf autoheader automake aclocal
-do
- uc=`echo $c | tr '[:lower:]' '[:upper:]'`
- eval "val=`echo '$'$uc`"
- if test -n "$val"
- then
- echo "$uc=$val in environment, will not attempt to auto-detect"
- continue
- fi
-
- case "$c" in
- autoconf|autoheader)
- VERNEEDED=`fgrep AC_PREREQ configure.ac | $AC_VERSIONGREP`
- VERSIONS="$AC_VERSIONS"
- pkg=autoconf
- ;;
- automake|aclocal)
- VERNEEDED=`fgrep AUTOMAKE_OPTIONS Makefile.am | $AM_VERSIONGREP`
- VERSIONS="$AM_VERSIONS"
- pkg=automake
- ;;
- esac
- printf "checking for $c ... "
- for x in `versioned_bins $c`; do
- ($x --version < /dev/null > /dev/null 2>&1) > /dev/null 2>&1
- if test $? -eq 0
- then
- echo $x
- eval $uc=$x
- break
- fi
- done
- eval "val=`echo '$'$uc`"
- if test -z "$val"
- then
- if test $c = $pkg
- then
- DIE="$DIE $c=$VERNEEDED"
- else
- DIE="$DIE $c($pkg)=$VERNEEDED"
- fi
- fi
-done
-
-if test -n "$DIE"
-then
- echo "You must have the following installed to compile $package:"
- for i in $DIE
- do
- printf ' '
- echo $i | sed -e 's/(/ (from /' -e 's/=\(.*\)/ (>= \1)/'
- done
- echo "Download the appropriate package(s) for your system,"
- echo "or get the source from one of the GNU ftp sites"
- echo "listed in http://www.gnu.org/order/ftp.html"
- exit 1
-fi
-
-echo "Generating configuration files for $package, please wait...."
-
-ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I m4"
-
-# /usr/share/aclocal is most likely included by default, already...
-ac_local_paths='
-/usr/local/share/aclocal
-/sw/share/aclocal
-/usr/pkg/share/aclocal
-/opt/share/aclocal
-/usr/gnu/share/aclocal
-'
-
-for i in $ac_local_paths; do
- if test -d "$i"; then
- ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $i"
- # we probably only want one of these...
- break
- fi
-done
-
-echo " $ACLOCAL $ACLOCAL_FLAGS"
-$ACLOCAL $ACLOCAL_FLAGS || exit 1
-
-echo " $AUTOHEADER"
-$AUTOHEADER || exit 1
-
-echo " $AUTOMAKE --add-missing $AUTOMAKE_FLAGS"
-$AUTOMAKE --add-missing $AUTOMAKE_FLAGS || exit 1
-
-echo " $AUTOCONF"
-$AUTOCONF || exit 1
-
-cd "$olddir"
-if test x$NOCONFIGURE = x; then
- "$srcdir"/configure "$@" || exit 1
-fi
+aclocal -I m4 $ACLOCAL_FLAGS
+autoheader
+automake --add-missing $AUTOMAKE_FLAGS
+autoconf
diff --git a/configure.ac b/configure.ac
index e253aa52f..5b4d577ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,20 +1,25 @@
AC_PREREQ(2.60)
-AC_INIT(mpd, 0.18.22, mpd-devel@musicpd.org)
+AC_INIT(mpd, 0.19.8, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
-VERSION_MINOR=18
-VERSION_REVISION=22
+VERSION_MINOR=19
+VERSION_REVISION=8
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
+AC_CONFIG_AUX_DIR(build)
AM_INIT_AUTOMAKE([foreign 1.11 dist-xz subdir-objects])
AM_SILENT_RULES
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_MACRO_DIR([m4])
-AC_DEFINE(PROTOCOL_VERSION, "0.18.0", [The MPD protocol version])
+AC_DEFINE(PROTOCOL_VERSION, "0.19.0", [The MPD protocol version])
+GIT_COMMIT=`GIT_DIR="$srcdir/.git" git describe --dirty --always 2>/dev/null`
+if test x$GIT_COMMIT != x; then
+ AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit])
+fi
dnl ---------------------------------------------------------------------------
dnl Programs
@@ -65,9 +70,31 @@ dnl OS Specific Defaults
dnl ---------------------------------------------------------------------------
AC_CANONICAL_HOST
+host_is_unix=yes
+host_is_linux=no
+host_is_android=no
host_is_darwin=no
+host_is_solaris=no
+host_is_windows=no
+
+linux_auto=no
case "$host_os" in
+linux-android*)
+ host_is_android=yes
+ host_is_linux=yes
+ linux_auto=auto
+ AM_CPPFLAGS="$AM_CPPFLAGS -DANDROID"
+ ;;
+
+linux*)
+ host_is_linux=yes
+ linux_auto=auto
+
+ dnl allow using all glibc features
+ CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+ ;;
+
mingw32* | windows*)
AC_CONFIG_FILES([
src/win32/mpd_win32_rc.rc
@@ -76,14 +103,21 @@ mingw32* | windows*)
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"
AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0600 -D_WIN32_WINNT=0x0600"
LIBS="$LIBS -lws2_32"
- HAVE_WINDOWS=1
+ host_is_windows=yes
+ host_is_unix=no
;;
darwin*)
host_is_darwin=yes
;;
+
+solaris*)
+ host_is_solaris=yes
+ ;;
esac
-AM_CONDITIONAL([HAVE_WINDOWS], [test x$HAVE_WINDOWS = x1])
+
+AM_CONDITIONAL([ANDROID], [test x$host_is_android = xyes])
+AM_CONDITIONAL([HAVE_WINDOWS], [test x$host_is_windows = xyes])
if test -z "$prefix" || test "x$prefix" = xNONE; then
local_lib=
@@ -122,6 +156,27 @@ if test -z "$prefix" || test "x$prefix" = xNONE; then
fi
dnl ---------------------------------------------------------------------------
+dnl Android
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_WITH([android-sdk],
+ AS_HELP_STRING([--with-android-sdk=DIR],
+ [Directory for Android SDK]),
+ [], [with_android_sdk=no])
+
+if test x$host_is_android = xyes; then
+ if test x$with_android_sdk = xno; then
+ AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR])
+ fi
+
+ if ! test -x $with_android_sdk/tools/android; then
+ AC_MSG_ERROR([Android SDK not found in $with_android_sdk])
+ fi
+fi
+
+AC_SUBST(ANDROID_SDK, [$with_android_sdk])
+
+dnl ---------------------------------------------------------------------------
dnl Language Checks
dnl ---------------------------------------------------------------------------
@@ -137,7 +192,8 @@ fi
dnl ---------------------------------------------------------------------------
dnl Header/Library Checks
dnl ---------------------------------------------------------------------------
-AC_CHECK_FUNCS(daemon fork)
+
+AC_SEARCH_LIBS([clock_gettime], [rt])
AC_SEARCH_LIBS([syslog], [bsd socket inet],
[AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])])
@@ -145,10 +201,16 @@ AC_SEARCH_LIBS([syslog], [bsd socket inet],
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl])
-AC_CHECK_FUNCS(pipe2 accept4)
-MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
-MPD_OPTIONAL_FUNC(signalfd, signalfd, USE_SIGNALFD)
-MPD_OPTIONAL_FUNC(epoll, epoll_create1, USE_EPOLL)
+if test x$host_is_linux = xyes; then
+ AC_CHECK_FUNCS(pipe2 accept4)
+fi
+
+AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
+
+if test x$host_is_linux = xyes; then
+ MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
+ MPD_OPTIONAL_FUNC(signalfd, signalfd, USE_SIGNALFD)
+fi
AC_SEARCH_LIBS([exp], [m],,
[AC_MSG_ERROR([exp() not found])])
@@ -156,14 +218,96 @@ AC_SEARCH_LIBS([exp], [m],,
AC_CHECK_HEADERS(locale.h)
AC_CHECK_HEADERS(valgrind/memcheck.h)
+AC_CHECK_HEADERS([sys/prctl.h], AC_CHECK_FUNCS([prctl]))
+
+AX_PTHREAD
+LIBS="$PTHREAD_LIBS $LIBS"
+AM_CFLAGS="$AM_CFLAGS $PTHREAD_CFLAGS"
+AM_CXXFLAGS="$AM_CXXFLAGS $PTHREAD_CFLAGS"
+
+AC_CHECK_LIB([pthread], [pthread_setname_np],
+ [have_pthread_setname_np=yes],
+ [have_pthread_setname_np=no])
+if test x$have_pthread_setname_np = xyes; then
+ AC_DEFINE(HAVE_PTHREAD_SETNAME_NP, 1, [Is pthread_setname_np() available?])
+fi
+
+dnl ---------------------------------------------------------------------------
+dnl Event loop selection
+dnl ---------------------------------------------------------------------------
+
+MPD_OPTIONAL_FUNC_NODEF(poll, poll)
+
+if test x$host_is_linux = xyes; then
+ MPD_OPTIONAL_FUNC_NODEF(epoll, epoll_create1)
+fi
+
+AC_ARG_WITH(pollmethod,
+ AS_HELP_STRING(
+ [--with-pollmethod=@<:@epoll|poll|winselect|auto@:>@],
+ [specify poll method for internal event loop (default=auto)]),,
+ [with_pollmethod=auto])
+
+if test "x$with_pollmethod" = xauto; then
+ if test "x$enable_epoll" = xyes; then
+ with_pollmethod=epoll
+ elif test "x$enable_poll" = xyes; then
+ with_pollmethod=poll
+ elif test "x$host_is_windows" = xyes; then
+ with_pollmethod=winselect
+ else
+ AC_MSG_ERROR([no poll method is available for your platform])
+ fi
+fi
+case "$with_pollmethod" in
+epoll)
+ AC_DEFINE(USE_EPOLL, 1, [Define to poll sockets with epoll])
+ ;;
+poll)
+ AC_DEFINE(USE_POLL, 1, [Define to poll sockets with poll])
+ ;;
+winselect)
+ AC_DEFINE(USE_WINSELECT, 1,
+ [Define to poll sockets with Windows select])
+ ;;
+*)
+ AC_MSG_ERROR([unknown pollmethod option: $with_pollmethod])
+esac
+
dnl ---------------------------------------------------------------------------
dnl Allow tools to be specifically built
dnl ---------------------------------------------------------------------------
+AC_ARG_ENABLE(database,
+ AS_HELP_STRING([--enable-database],
+ [enable support for the music database]),,
+ enable_database=yes)
+AM_CONDITIONAL(ENABLE_DATABASE, test x$enable_database = xyes)
+if test x$enable_database = xyes; then
+ database_auto=auto
+ AC_DEFINE(ENABLE_DATABASE, 1, [Define to enable the music database])
+else
+ database_auto=no
+fi
+
AC_ARG_ENABLE(libmpdclient,
AS_HELP_STRING([--enable-libmpdclient],
[enable support for the MPD client]),,
enable_libmpdclient=auto)
+MPD_DEPENDS([enable_libmpdclient], [enable_database],
+ [Cannot use --enable-libmpdclient with --disable-database])
+
+AC_ARG_ENABLE(expat,
+ AS_HELP_STRING([--enable-expat],
+ [enable the expat XML parser]),,
+ enable_expat=auto)
+
+AC_ARG_ENABLE(upnp,
+ AS_HELP_STRING([--enable-upnp],
+ [enable UPnP client support (default: auto)]),,
+ enable_upnp=auto)
+MPD_DEPENDS([enable_upnp], [enable_database],
+ [Cannot use --enable-upnp with --disable-database])
AC_ARG_ENABLE(adplug,
AS_HELP_STRING([--enable-adplug],
@@ -172,7 +316,7 @@ AC_ARG_ENABLE(adplug,
AC_ARG_ENABLE(alsa,
AS_HELP_STRING([--enable-alsa], [enable ALSA support]),,
- [enable_alsa=auto])
+ [enable_alsa=$linux_auto])
AC_ARG_ENABLE(roar,
AS_HELP_STRING([--enable-roar],
@@ -183,12 +327,19 @@ AC_ARG_ENABLE(ao,
AS_HELP_STRING([--enable-ao],
[enable support for libao]),,
enable_ao=auto)
+MPD_DEPENDS([enable_ao], [enable_glib],
+ [Cannot use --enable-ao with --disable-glib])
AC_ARG_ENABLE(audiofile,
AS_HELP_STRING([--enable-audiofile],
[enable audiofile support (WAV and others)]),,
enable_audiofile=auto)
+AC_ARG_ENABLE(zlib,
+ AS_HELP_STRING([--enable-zlib],
+ [enable zlib support (default: auto)]),,
+ enable_zlib=auto)
+
AC_ARG_ENABLE(bzip2,
AS_HELP_STRING([--enable-bzip2],
[enable bzip2 archive support (default: auto)]),,
@@ -198,12 +349,24 @@ AC_ARG_ENABLE(cdio-paranoia,
AS_HELP_STRING([--enable-cdio-paranoia],
[enable support for audio CD support]),,
enable_cdio_paranoia=auto)
+MPD_DEPENDS([enable_cdio_paranoia], [enable_glib],
+ [Cannot use --enable-cdio-paranoia with --disable-glib])
AC_ARG_ENABLE(curl,
AS_HELP_STRING([--enable-curl],
[enable support for libcurl HTTP streaming (default: auto)]),,
[enable_curl=auto])
+AC_ARG_ENABLE(smbclient,
+ AS_HELP_STRING([--enable-smbclient],
+ [enable support for libsmbclient (default: auto)]),,
+ [enable_smbclient=auto])
+
+AC_ARG_ENABLE(nfs,
+ AS_HELP_STRING([--enable-nfs],
+ [enable support for libnfs (default: auto)]),,
+ [enable_nfs=auto])
+
AC_ARG_ENABLE(debug,
AS_HELP_STRING([--enable-debug],
[enable debugging (default: disabled)]),,
@@ -243,11 +406,15 @@ AC_ARG_ENABLE(gme,
AS_HELP_STRING([--enable-gme],
[enable Blargg's game music emulator plugin]),,
enable_gme=auto)
+MPD_DEPENDS([enable_gme], [enable_glib],
+ [Cannot use --enable-gme with --disable-glib])
AC_ARG_ENABLE(httpd-output,
AS_HELP_STRING([--enable-httpd-output],
[enables the HTTP server output]),,
[enable_httpd_output=auto])
+MPD_DEPENDS([enable_httpd_output], [enable_glib],
+ [Cannot use --enable-httpd-output with --disable-glib])
AC_ARG_ENABLE(id3,
AS_HELP_STRING([--enable-id3],
@@ -273,6 +440,8 @@ AC_ARG_ENABLE(jack,
AS_HELP_STRING([--enable-jack],
[enable jack support]),,
enable_jack=auto)
+MPD_DEPENDS([enable_jack], [enable_glib],
+ [Cannot use --enable-jack with --disable-glib])
AC_SYS_LARGEFILE
@@ -285,6 +454,8 @@ AC_ARG_ENABLE(soundcloud,
AS_HELP_STRING([--enable-soundcloud],
[enable support for soundcloud.com]),,
[enable_soundcloud=auto])
+MPD_DEPENDS([enable_soundcloud], [enable_glib],
+ [Cannot use --enable-soundcloud with --disable-glib])
AC_ARG_ENABLE(lame-encoder,
AS_HELP_STRING([--enable-lame-encoder],
@@ -300,6 +471,11 @@ AC_ARG_ENABLE(lsr,
[enable libsamplerate support]),,
enable_lsr=auto)
+AC_ARG_ENABLE(soxr,
+ AS_HELP_STRING([--enable-soxr],
+ [enable the libsoxr resampler]),,
+ enable_soxr=auto)
+
AC_ARG_ENABLE(mad,
AS_HELP_STRING([--enable-mad],
[enable libmad mp3 decoder plugin]),,
@@ -369,7 +545,13 @@ AC_ARG_ENABLE(sidplay,
AS_HELP_STRING([--enable-sidplay],
[enable C64 SID support via libsidplay2]),,
enable_sidplay=auto)
+MPD_DEPENDS([enable_sidplay], [enable_glib],
+ [Cannot use --enable-sidplay with --disable-glib])
+AC_ARG_ENABLE(shine-encoder,
+ AS_HELP_STRING([--enable-shine-encoder],
+ [enables shine encoder]),,
+ [enable_shine_encoder=auto])
AC_ARG_ENABLE(shout,
AS_HELP_STRING([--enable-shout],
@@ -384,17 +566,19 @@ AC_ARG_ENABLE(sndfile,
AC_ARG_ENABLE(solaris_output,
AS_HELP_STRING([--enable-solaris-output],
[enables the Solaris /dev/audio output]),,
- [enable_solaris_output=auto])
+ [enable_solaris_output=$host_is_solaris])
AC_ARG_ENABLE(sqlite,
AS_HELP_STRING([--enable-sqlite],
[enable support for the SQLite database]),,
- [enable_sqlite=auto])
+ [enable_sqlite=$database_auto])
+MPD_DEPENDS([enable_sqlite], [enable_glib],
+ [Cannot use --enable-sqlite with --disable-glib])
AC_ARG_ENABLE(systemd-daemon,
AS_HELP_STRING([--enable-systemd-daemon],
[use the systemd daemon library (default=auto)]),,
- [enable_systemd_daemon=auto])
+ [enable_systemd_daemon=$linux_auto])
AC_ARG_ENABLE(tcp,
AS_HELP_STRING([--disable-tcp],
@@ -419,7 +603,7 @@ AC_ARG_ENABLE(twolame-encoder,
AC_ARG_ENABLE(un,
AS_HELP_STRING([--disable-un],
[disable support for clients connecting via unix domain sockets (default: enable)]),,
- [enable_un=yes])
+ [enable_un=$host_is_unix])
AC_ARG_ENABLE(vorbis,
AS_HELP_STRING([--enable-vorbis],
@@ -430,6 +614,8 @@ AC_ARG_ENABLE(vorbis-encoder,
AS_HELP_STRING([--enable-vorbis-encoder],
[enable the Ogg Vorbis encoder]),,
[enable_vorbis_encoder=auto])
+MPD_DEPENDS([enable_vorbis_encoder], [enable_glib],
+ [Cannot use --enable-vorbis-encoder with --disable-glib])
AC_ARG_ENABLE(wave-encoder,
AS_HELP_STRING([--enable-wave-encoder],
@@ -440,6 +626,8 @@ AC_ARG_ENABLE(wavpack,
AS_HELP_STRING([--enable-wavpack],
[enable WavPack support]),,
enable_wavpack=auto)
+MPD_DEPENDS([enable_wavpack], [enable_glib],
+ [Cannot use --enable-wavpack with --disable-glib])
AC_ARG_ENABLE(werror,
AS_HELP_STRING([--enable-werror],
@@ -475,13 +663,58 @@ AC_ARG_WITH(tremor-includes,
dnl ---------------------------------------------------------------------------
dnl Mandatory Libraries
dnl ---------------------------------------------------------------------------
-PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
+
+no_exceptions=yes
+
+AX_BOOST_BASE([1.46],, [AC_MSG_ERROR([Boost not found])])
+
+dnl Don't disable exceptions on Boost older than 1.54, because
+dnl Boost.Intrusive supports this compiler mode only since 1.54;
+dnl see https://svn.boost.org/trac/boost/ticket/7849
+CPPFLAGS_SAVED="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+export CPPFLAGS
+AC_LANG_PUSH(C++)
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+@%:@include <boost/version.hpp>
+]], [[
+#if BOOST_VERSION < 105400
+#error detected Boost older than 1.54
+#endif
+]])],, [no_exceptions=no])
+AC_LANG_POP([C++])
+CPPFLAGS="$CPPFLAGS_SAVED"
+
+AC_ARG_ENABLE(icu,
+ AS_HELP_STRING([--enable-icu],
+ [enable libicu for Unicode (default: enabled)]),,
+ enable_icu=yes)
+
+if test x$enable_icu = xyes; then
+ PKG_CHECK_MODULES([ICU], [icu-i18n],,
+ [AC_MSG_ERROR([libicu not found])])
+
+ AC_DEFINE(HAVE_ICU, 1, [Define if libicu is used])
+fi
+AM_CONDITIONAL(HAVE_ICU, test x$enable_icu = xyes)
+
+AC_ARG_ENABLE(glib,
+ AS_HELP_STRING([--enable-glib],
+ [enable GLib usage (default: enabled)]),,
+ enable_glib=yes)
+
+if test x$enable_glib = xyes; then
+ PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
[AC_MSG_ERROR([GLib 2.28 is required])])
-if test x$GCC = xyes; then
- # suppress warnings in the GLib headers
- GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
+ if test x$GCC = xyes; then
+ # suppress warnings in the GLib headers
+ GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
+ fi
+
+ AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used])
fi
+AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
dnl ---------------------------------------------------------------------------
dnl Protocol Options
@@ -519,12 +752,6 @@ if test x$enable_tcp = xyes; then
AC_DEFINE(HAVE_TCP, 1, [Define if TCP socket support is enabled])
fi
-case "$host_os" in
-mingw* | windows* | cygwin*)
- enable_un=no
- ;;
-esac
-
if test x$enable_un = xyes; then
AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
STRUCT_UCRED
@@ -565,6 +792,15 @@ fi
AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes)
+dnl -------------------------------- expat --------------------------------
+MPD_AUTO_PKG(expat, EXPAT, [expat],
+ [expat XML parser], [expat not found])
+if test x$enable_expat = xyes; then
+ AC_DEFINE(HAVE_EXPAT, 1, [Define to use the expat XML parser])
+fi
+
+AM_CONDITIONAL(HAVE_EXPAT, test x$enable_expat = xyes)
+
dnl --------------------------------- inotify ---------------------------------
AC_CHECK_FUNCS(inotify_init inotify_init1)
@@ -644,7 +880,7 @@ avahi)
;;
esac
-MPD_AUTO_PKG(avahi, AVAHI, [avahi-client],
+MPD_AUTO_PKG(avahi, AVAHI, [avahi-client dbus-1],
[avahi client library], [avahi-client not found])
if test x$enable_avahi = xyes; then
AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])
@@ -697,21 +933,22 @@ dnl Converter Plugins
dnl ---------------------------------------------------------------------------
dnl ------------------------------ libsamplerate ------------------------------
-MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.0.15],
+MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.1.3],
[libsamplerate resampling], [libsamplerate not found])
if test x$enable_lsr = xyes; then
AC_DEFINE([HAVE_LIBSAMPLERATE], 1,
[Define to enable libsamplerate])
fi
+AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes)
-if test x$enable_lsr = xyes; then
- PKG_CHECK_MODULES([SAMPLERATE_013],
- [samplerate >= 0.1.3],,
- [AC_DEFINE([HAVE_LIBSAMPLERATE_NOINT], 1,
- [libsamplerate doesn't provide src_int_to_float_array() (<0.1.3)])])
+dnl ------------------------------ libsoxr ------------------------------------
+MPD_AUTO_PKG(soxr, SOXR, [soxr],
+ [libsoxr resampler], [libsoxr not found])
+if test x$enable_soxr = xyes; then
+ AC_DEFINE([HAVE_SOXR], 1, [Define to enable libsoxr])
fi
-AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes)
+AM_CONDITIONAL(HAVE_SOXR, test x$enable_soxr = xyes)
dnl ---------------------------------------------------------------------------
dnl Input Plugins
@@ -725,6 +962,23 @@ if test x$enable_curl = xyes; then
fi
AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes)
+dnl ----------------------------------- smbclient -----------------------------
+MPD_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
+ [smbclient], [smbc_init], [-lsmbclient], [],
+ [smbclient input plugin], [libsmbclient not found])
+if test x$enable_smbclient = xyes; then
+ AC_DEFINE(ENABLE_SMBCLIENT, 1, [Define when libsmbclient is used])
+fi
+AM_CONDITIONAL(ENABLE_SMBCLIENT, test x$enable_smbclient = xyes)
+
+dnl ----------------------------------- NFS -----------------------------
+MPD_AUTO_PKG(nfs, NFS, [libnfs],
+ [NFS input plugin], [libnfs not found])
+if test x$enable_nfs = xyes; then
+ AC_DEFINE(ENABLE_NFS, 1, [Define when libnfs is used])
+fi
+AM_CONDITIONAL(ENABLE_NFS, test x$enable_nfs = xyes)
+
dnl --------------------------------- Despotify ---------------------------------
MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
[Despotify support], [despotify not found])
@@ -769,6 +1023,30 @@ fi
AM_CONDITIONAL(ENABLE_MMS, test x$enable_mms = xyes)
dnl ---------------------------------------------------------------------------
+dnl Neighbor Plugins
+dnl ---------------------------------------------------------------------------
+
+AC_ARG_ENABLE(neighbor-plugins,
+ AS_HELP_STRING([--enable-neighbor-plugins],
+ [enable support for neighbor discovery (default: auto)]),,
+ [enable_neighbor_plugins=auto])
+
+if test x$enable_neighbor_plugins = xauto; then
+ if test x$enable_smbclient = xyes; then
+ enable_neighbor_plugins=yes
+ fi
+ if test x$enable_upnp = xyes; then
+ enable_neighbor_plugins=yes
+ fi
+fi
+
+if test x$enable_neighbor_plugins = xyes; then
+ AC_DEFINE(ENABLE_NEIGHBOR_PLUGINS, 1,
+ [Define to enable support for neighbor discovery])
+fi
+AM_CONDITIONAL(ENABLE_NEIGHBOR_PLUGINS, test x$enable_neighbor_plugins = xyes)
+
+dnl ---------------------------------------------------------------------------
dnl Archive Plugins
dnl ---------------------------------------------------------------------------
@@ -787,6 +1065,16 @@ fi
AM_CONDITIONAL(ENABLE_ISO9660_TEST, test x$MKISOFS != xno)
+dnl ---------------------------------- zlib ---------------------------------
+
+MPD_AUTO_PKG(zlib, ZLIB, [zlib],
+ [zlib support], [zlib not found])
+
+AM_CONDITIONAL(HAVE_ZLIB, test x$enable_zlib = xyes)
+if test x$enable_zlib = xyes; then
+ AC_DEFINE(HAVE_ZLIB, 1, [Define to enable zlib support])
+fi
+
dnl ---------------------------------- libbz2 ---------------------------------
MPD_AUTO_LIB(bzip2, BZ2, bz2, BZ2_bzDecompressInit, [-lbz2], [],
@@ -803,6 +1091,24 @@ fi
AM_CONDITIONAL(ENABLE_BZIP2_TEST, test x$BZIP2 != xno)
+dnl ---------------------------------- libupnp ---------------------------------
+
+if test x$enable_expat = xno; then
+ if test x$enable_upnp = xauto; then
+ AC_MSG_WARN([expat disabled -- disabling UPnP])
+ enable_upnp=no
+ elif test x$enable_upnp = xyes; then
+ AC_MSG_ERROR([expat disabled -- required for UPnP])
+ fi
+fi
+
+MPD_AUTO_PKG(upnp, UPNP, [libupnp],
+ [UPnP client support], [libupnp not found])
+if test x$enable_upnp = xyes; then
+ AC_DEFINE(HAVE_LIBUPNP, 1, [Define when libupnp is used])
+fi
+AM_CONDITIONAL(HAVE_LIBUPNP, test x$enable_upnp = xyes)
+
dnl --------------------------------- libzzip ---------------------------------
MPD_AUTO_PKG(zzip, ZZIP, [zziplib >= 0.13],
[libzzip archive library], [libzzip not found])
@@ -1119,6 +1425,7 @@ else
enable_vorbis_encoder=no
enable_lame_encoder=no
enable_twolame_encoder=no
+ enable_shine_encoder=no
enable_wave_encoder=no
enable_flac_encoder=no
fi
@@ -1130,6 +1437,17 @@ if test x$enable_flac_encoder = xyes; then
fi
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
+dnl ------------------------------- Shine Encoder ------------------------------
+
+MPD_AUTO_PKG(shine_encoder, SHINE, [shine >= 3.1],
+ [shine encoder], [libshine not found])
+
+if test x$enable_shine_encoder = xyes; then
+ AC_DEFINE(ENABLE_SHINE_ENCODER, 1,
+ [Define to enable the shine encoder plugin])
+fi
+AM_CONDITIONAL(ENABLE_SHINE_ENCODER, test x$enable_shine_encoder = xyes)
+
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
[Ogg Vorbis encoder], [libvorbisenc not found])
@@ -1173,6 +1491,7 @@ if test x$enable_vorbis_encoder != xno ||
test x$enable_lame_encoder != xno ||
test x$enable_twolame_encoder != xno ||
test x$enable_flac_encoder != xno ||
+ test x$enable_shine_encoder != xno ||
test x$enable_wave_encoder != xno; then
# at least one encoder plugin is enabled
enable_encoder=yes
@@ -1368,18 +1687,6 @@ AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes)
dnl --------------------------------- Solaris ---------------------------------
-if test x$enable_solaris_output = xauto; then
- case "$host_os" in
- solaris*)
- enable_solaris_output=yes
- ;;
-
- *)
- enable_solaris_output=no
- ;;
- esac
-fi
-
if test x$enable_solaris_output = xyes; then
AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support])
fi
@@ -1388,17 +1695,13 @@ AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes)
dnl --------------------------------- WinMM ---------------------------------
-case "$host_os" in
- mingw32* | windows*)
- AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
- enable_winmm_output=yes
- LIBS="$LIBS -lwinmm"
- ;;
-
- *)
- enable_winmm_output=no
- ;;
-esac
+if test "x$host_is_windows" = xyes; then
+ AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
+ enable_winmm_output=yes
+ LIBS="$LIBS -lwinmm"
+else
+ enable_winmm_output=no
+fi
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
@@ -1407,8 +1710,11 @@ dnl Documentation
dnl ---------------------------------------------------------------------------
if test x$enable_documentation = xyes; then
AC_PATH_PROG(XMLTO, xmlto)
+ if test x$XMLTO = x; then
+ AC_MSG_ERROR([xmlto not found])
+ fi
+
AC_SUBST(XMLTO)
- AM_CONDITIONAL(HAVE_XMLTO, test x$XMLTO != x)
AC_PATH_PROG(DOXYGEN, doxygen)
if test x$DOXYGEN = x; then
@@ -1416,8 +1722,6 @@ if test x$enable_documentation = xyes; then
fi
AC_SUBST(DOXYGEN)
-else
- AM_CONDITIONAL(HAVE_XMLTO, false)
fi
AM_CONDITIONAL(ENABLE_DOCUMENTATION, test x$enable_documentation = xyes)
@@ -1452,8 +1756,12 @@ AC_LANG_PUSH([C++])
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])
+
+if test x$no_exceptions = xyes; then
+ AX_APPEND_COMPILE_FLAGS([-fno-exceptions])
+ AX_APPEND_COMPILE_FLAGS([-fno-rtti])
+fi
+
AX_APPEND_COMPILE_FLAGS([-ffast-math])
AX_APPEND_COMPILE_FLAGS([-ftree-vectorize])
AC_LANG_POP
@@ -1520,6 +1828,10 @@ results(ipv6, "IPv6")
results(tcp, "TCP")
results(un,[UNIX Domain Sockets])
+printf '\nStorage support:\n\t'
+results(nfs, [NFS])
+results(smbclient, [SMB])
+
printf '\nFile format support:\n\t'
results(aac, [AAC])
results(adplug, [AdPlug])
@@ -1546,6 +1858,7 @@ results(wildmidi, [WildMidi])
printf '\nOther features:\n\t'
results(lsr, [libsamplerate])
+results(soxr, [libsoxr])
results(libmpdclient, [libmpdclient])
results(inotify, [inotify])
results(sqlite, [SQLite])
@@ -1579,6 +1892,7 @@ if
printf '\nStreaming encoder support:\n\t'
results(flac_encoder, [FLAC])
results(lame_encoder, [LAME])
+ results(shine_encoder, [Shine])
results(vorbis_encoder, [Ogg Vorbis])
results(opus, [Opus])
results(twolame_encoder, [TwoLAME])
@@ -1588,11 +1902,15 @@ fi
printf '\nStreaming support:\n\t'
results(cdio_paranoia, [CDIO_PARANOIA])
results(curl,[CURL])
+results(smbclient,[SMBCLIENT])
results(despotify,[Despotify])
results(soundcloud,[Soundcloud])
printf '\n\t'
results(mms,[MMS])
+printf '\nEvent loop:\n\t'
+printf $with_pollmethod
+
printf '\n\n##########################################\n\n'
echo 'Generating files needed for compilation'
@@ -1602,7 +1920,7 @@ dnl Generate files
dnl ---------------------------------------------------------------------------
AC_CONFIG_FILES(Makefile)
AC_CONFIG_FILES(doc/doxygen.conf)
-AC_CONFIG_FILES(mpd.service)
+AC_CONFIG_FILES(systemd/mpd.service)
AC_OUTPUT
echo 'MPD is ready for compilation, type "make" to begin.'
diff --git a/doc/developer.xml b/doc/developer.xml
index 729e6a513..aa3cc62c0 100644
--- a/doc/developer.xml
+++ b/doc/developer.xml
@@ -10,7 +10,7 @@
<para>
This is a guide for those who wish to hack on the MPD source
code. MPD is an open project, and we are always happy about
- contributions. So far, more than 50 people have contributed
+ contributions. So far, more than 150 people have contributed
patches.
</para>
@@ -155,7 +155,53 @@ foo(const char *abc, int xyz)
<para>
Send your patches to the mailing list:
- mpd-devel@musicpd.org
+ <email>mpd-devel@musicpd.org</email> (<ulink
+ url="http://mailman.blarg.de/listinfo/mpd-devel">subscribe
+ here</ulink>)
</para>
+
+ <para>
+ <command>git pull</command> requests are preferred. Regular
+ contributors can get <ulink
+ url="http://git.musicpd.org/account-policy.html">an account on
+ git.musicpd.org</ulink>, but any public git repository will do.
+ </para>
+ </chapter>
+
+ <chapter>
+ <title>Development Tools</title>
+
+ <section>
+ <title>Clang Static Analyzer</title>
+
+ <para>
+ The <ulink url="http://clang-analyzer.llvm.org/">clang static
+ analyzer</ulink> is a tool that helps find bugs. To run it on
+ the MPD code base, install LLVM and clang. Configure MPD to
+ use clang:
+ </para>
+
+ <programlisting>./configure --enable-debug CXX=clang++ CC=clang ...</programlisting>
+
+ <para>
+ It is recommended to use <option>--enable-debug</option>,
+ because the analyzer takes advantage of
+ <function>assert()</function> calls, which are only enabled in
+ the debug build.
+ </para>
+
+ <para>
+ Now run the analyzer:
+ </para>
+
+ <programlisting>scan-build --use-c++=clang++ --use-cc=clang make</programlisting>
+
+ <para>
+ The options <option>--use-c++</option> and
+ <option>--use-cc</option> are necessary because it invokes
+ <command>cc</command> for actually compiling the sources by
+ default. That breaks, because MPD requires a C99 compiler.
+ </para>
+ </section>
</chapter>
</book>
diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5
index 6431613d1..5ff8608bb 100644
--- a/doc/mpd.conf.5
+++ b/doc/mpd.conf.5
@@ -136,53 +136,6 @@ for the format of this parameter. Multiple audio_output sections may be
specified. If no audio_output section is specified, then MPD will scan for a
usable audio output.
.TP
-.B audio_output_format <sample_rate:bits:channels>
-This specifies the sample rate, bits per sample, and number of channels of
-audio that is sent to each audio output. Note that audio outputs may specify
-their own audio format which will be used for actual output to the audio
-device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The
-default is to use the audio format of the input file.
-Any of the three attributes may be an asterisk to specify that this
-attribute should not be enforced
-.TP
-.B samplerate_converter <integer or prefix>
-This specifies the libsamplerate converter to use. The supplied value should
-either be an integer or a prefix of the name of a converter. The default is
-"Fastest Sinc Interpolator".
-
-At the time of this writing, the following converters are available:
-.RS
-.TP
-Best Sinc Interpolator (0)
-
-Band limited sinc interpolation, best quality, 97dB SNR, 96% BW.
-.TP
-Medium Sinc Interpolator (1)
-
-Band limited sinc interpolation, medium quality, 97dB SNR, 90% BW.
-.TP
-Fastest Sinc Interpolator (2)
-
-Band limited sinc interpolation, fastest, 97dB SNR, 80% BW.
-.TP
-ZOH Interpolator (3)
-
-Zero order hold interpolator, very fast, very poor quality with audible
-distortions.
-.TP
-Linear Interpolator (4)
-
-Linear interpolator, very fast, poor quality.
-.TP
-internal
-
-Poor quality, no floating point operations. This is the default (and
-only choice) if MPD was compiled without libsamplerate.
-.RE
-.IP
-For an up-to-date list of available converters, please see the libsamplerate
-documentation (available online at <\fBhttp://www.mega\-nerd.com/SRC/\fP>).
-.TP
.B replaygain <off or album or track or auto>
If specified, mpd will adjust the volume of songs played using ReplayGain tags
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
@@ -198,39 +151,6 @@ This is the gain (in dB) applied to songs with ReplayGain tags.
.B volume_normalization <yes or no>
If yes, mpd will normalize the volume of songs as they play. The default is no.
.TP
-.B audio_buffer_size <size in KiB>
-This specifies the size of the audio buffer in kibibytes. The default is 4096,
-large enough for nearly 12 seconds of CD-quality audio.
-.TP
-.B buffer_before_play <0-100%>
-This specifies how much of the audio buffer should be filled before playing a
-song. Try increasing this if you hear skipping when manually changing songs.
-The default is 10%, a little over 1 second of CD-quality audio with the default
-buffer size.
-.TP
-.B http_proxy_host <hostname>
-This setting is deprecated. Use the "proxy" setting in the "curl"
-input block. See MPD user manual for details.
-.TP
-.B connection_timeout <seconds>
-If a client does not send any new data in this time period, the connection is
-closed. The default is 60.
-.TP
-.B max_connections <number>
-This specifies the maximum number of clients that can be connected to mpd. The
-default is 5.
-.TP
-.B max_playlist_length <number>
-This specifies the maximum number of songs that can be in the playlist. The
-default is 16384.
-.TP
-.B max_command_list_size <size in KiB>
-This specifies the maximum size a command list can be. The default is 2048.
-.TP
-.B max_output_buffer_size <size in KiB>
-This specifies the maximum size of the output buffer to a client. The default
-is 8192.
-.TP
.B filesystem_charset <charset>
This specifies the character set used for the filesystem. A list of supported
character sets can be obtained by running "iconv \-l". The default is
@@ -260,7 +180,8 @@ clients. Note that you must recreate (not update) your database for changes to
this parameter to take effect. Possible values are artist, album, title,
track, name, genre, date, composer, performer, comment, disc,
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
-musicbrainz_trackid. Multiple tags may be specified as a comma separated list.
+musicbrainz_releasetrackid, musicbrainz_trackid. Multiple tags may be specified
+as a comma separated list.
An example value is "artist,album,title,track". The special value "none" may
be used alone to disable all metadata. The default is to use all known tag
types except for comments and those starting with "musicbrainz".
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index 470a5c98e..4b55f8801 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -373,38 +373,6 @@ input {
#
###############################################################################
-
-# MPD Internal Buffering ######################################################
-#
-# This setting adjusts the size of internal decoded audio buffering. Changing
-# this may have undesired effects. Don't change this if you don't know what you
-# are doing.
-#
-#audio_buffer_size "4096"
-#
-# This setting controls the percentage of the buffer which is filled before
-# beginning to play. Increasing this reduces the chance of audio file skipping,
-# at the cost of increased time prior to audio playback.
-#
-#buffer_before_play "10%"
-#
-###############################################################################
-
-
-# Resource Limitations ########################################################
-#
-# These settings are various limitations to prevent MPD from using too many
-# resources. Generally, these settings should be minimized to prevent security
-# risks, depending on the operating resources.
-#
-#connection_timeout "60"
-#max_connections "10"
-#max_playlist_length "16384"
-#max_command_list_size "2048"
-#max_output_buffer_size "8192"
-#
-###############################################################################
-
# Character Encoding ##########################################################
#
# If file or directory names do not display correctly for your locale then you
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 5aa9c9114..05468e535 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -4,18 +4,18 @@
<book>
<title>The Music Player Daemon protocol</title>
- <chapter>
+ <chapter id="syntax">
<title>General protocol syntax</title>
<section>
<title>Protocol overview</title>
<para>
- The MPD command protocol exchanges line-based text records
- between client and server over TCP. Once the client is
- connected to the server, they conduct a conversation until the
- client closes the connection. The conversation flow is always
- initiated by the client.
+ The <application>MPD</application> command protocol exchanges
+ line-based text records between client and server over TCP.
+ Once the client is connected to the server, they conduct a
+ conversation until the client closes the connection. The
+ conversation flow is always initiated by the client.
</para>
<para>
@@ -38,7 +38,7 @@
</para>
</section>
- <section>
+ <section id="request_syntax">
<title>Requests</title>
<cmdsynopsis>
@@ -70,7 +70,7 @@
</para>
</section>
- <section>
+ <section id="response_syntax">
<title>Responses</title>
<para>
@@ -79,7 +79,7 @@
denote the end of command execution.
</para>
- <section>
+ <section id="failure_response_syntax">
<title>Failure responses</title>
<para>
@@ -188,7 +188,7 @@
</para>
</section>
- <section>
+ <section id="range_syntax">
<title>Ranges</title>
<para>
@@ -203,21 +203,21 @@
</section>
</chapter>
- <chapter>
+ <chapter id="recipes">
<title>Recipes</title>
- <section>
+ <section id="queuing_recipe">
<title>Queuing</title>
<para>
- Often, users run MPD with "<link
+ Often, users run <application>MPD</application> with "<link
linkend="command_random">random</link>" enabled, but want to
be able to insert songs "before" the rest of the playlist.
That is commonly called "queuing".
</para>
<para>
- MPD implements this by allowing the client to specify a
+ <application>MPD</application> implements this by allowing the client to specify a
"priority" for each song in the playlist (commands <link
linkend="command_prio"><command>prio</command></link> and
<link
@@ -227,24 +227,25 @@
</para>
<para>
- In "random" mode, MPD maintains an internal randomized
- sequence of songs. In this sequence, songs with a higher
- priority come first, and all songs with the same priority are
- shuffled (by default, all songs are shuffled, because all have
- the same priority "0"). When you increase the priority of a
- song, it is moved to the front of the sequence according to
- its new priority, but always after the current one. A song
- that has been played already (it's "before" the current song
- in that sequence) will only be scheduled for repeated playback
- if its priority has become bigger than the priority of the
- current song. Decreasing the priority of a song will moved it
- farther to the end of the sequence. Changing the priority of
- the current song has no effect on the sequence.
+ In "random" mode, <application>MPD</application> maintains an
+ internal randomized sequence of songs. In this sequence,
+ songs with a higher priority come first, and all songs with
+ the same priority are shuffled (by default, all songs are
+ shuffled, because all have the same priority "0"). When you
+ increase the priority of a song, it is moved to the front of
+ the sequence according to its new priority, but always after
+ the current one. A song that has been played already (it's
+ "before" the current song in that sequence) will only be
+ scheduled for repeated playback if its priority has become
+ bigger than the priority of the current song. Decreasing the
+ priority of a song will moved it farther to the end of the
+ sequence. Changing the priority of the current song has no
+ effect on the sequence.
</para>
</section>
</chapter>
- <chapter>
+ <chapter id="command_reference">
<title>Command reference</title>
<note>
@@ -255,12 +256,12 @@
commands using song ids should be used instead of the commands
that manipulate and control playback based on playlist
position. Using song ids is a safer method when multiple
- clients are interacting with MPD.
+ clients are interacting with <application>MPD</application>.
</para>
</note>
- <section>
- <title>Querying MPD's status</title>
+ <section id="status_commands">
+ <title>Querying <application>MPD</application>'s status</title>
<variablelist>
<varlistentry id="command_clearerror">
@@ -298,12 +299,14 @@
</term>
<listitem>
<para>
- <footnote id="since_0_14"><simpara>Introduced with MPD 0.14</simpara></footnote>
+ <footnote id="since_0_14"><simpara>Introduced with
+ <application>MPD</application> 0.14</simpara></footnote>
Waits until there is a noteworthy change in one or more
- of MPD's subsystems. As soon as there is one, it lists
- all changed systems in a line in the format
- <returnvalue>changed: SUBSYSTEM</returnvalue>, where
- SUBSYSTEM is one of the following:
+ of <application>MPD</application>'s subsystems. As soon
+ as there is one, it lists all changed systems in a line
+ in the format <returnvalue>changed:
+ SUBSYSTEM</returnvalue>, where SUBSYSTEM is one of the
+ following:
</para>
<itemizedlist>
<listitem>
@@ -385,14 +388,15 @@
to wait for events as long as mpd runs. The
<command>idle</command> command can be canceled by
sending the command <command>noidle</command> (no other
- commands are allowed). MPD will then leave
- <command>idle</command> mode and print results
- immediately; might be empty at this time.
+ commands are allowed). <application>MPD</application>
+ will then leave <command>idle</command> mode and print
+ results immediately; might be empty at this time.
</para>
<para>
- If the optional <varname>SUBSYSTEMS</varname> argument is used,
- MPD will only send notifications when something changed in
- one of the specified subsytems.
+ If the optional <varname>SUBSYSTEMS</varname> argument
+ is used, <application>MPD</application> will only send
+ notifications when something changed in one of the
+ specified subsytems.
</para>
</listitem>
</varlistentry>
@@ -429,7 +433,7 @@
<listitem>
<para>
<varname>single</varname>:
- <footnote id="since_0_15"><simpara>Introduced with MPD 0.15</simpara></footnote>
+ <footnote id="since_0_15"><simpara>Introduced with <application>MPD</application> 0.15</simpara></footnote>
<returnvalue>0 or 1</returnvalue>
</para>
</listitem>
@@ -504,7 +508,7 @@
<listitem>
<para>
<varname>elapsed</varname>:
- <footnote id="since_0_16"><simpara>Introduced with MPD 0.16</simpara></footnote>
+ <footnote id="since_0_16"><simpara>Introduced with <application>MPD</application> 0.16</simpara></footnote>
<returnvalue>
Total time elapsed within the current song, but
with higher resolution.
@@ -612,7 +616,7 @@
</variablelist>
</section>
- <section>
+ <section id="playback_option_commands">
<title>Playback options</title>
<variablelist>
@@ -745,7 +749,7 @@
<parameter>album</parameter>,
<parameter>auto</parameter><footnote
id="replay_gain_auto_since_0_16">
- <simpara>added in MPD 0.16</simpara>
+ <simpara>added in <application>MPD</application> 0.16</simpara>
</footnote>.
</para>
<para>
@@ -795,7 +799,7 @@
</variablelist>
</section>
- <section>
+ <section id="playback_commands">
<title>Controlling playback</title>
<variablelist>
@@ -882,8 +886,8 @@
<listitem>
<para>
Seeks to the position <varname>TIME</varname> (in
- seconds) of entry <varname>SONGPOS</varname> in the
- playlist.
+ seconds; fractions allowed) of entry
+ <varname>SONGPOS</varname> in the playlist.
</para>
</listitem>
</varlistentry>
@@ -898,7 +902,8 @@
<listitem>
<para>
Seeks to the position <varname>TIME</varname> (in
- seconds) of song <varname>SONGID</varname>.
+ seconds; fractions allowed) of song
+ <varname>SONGID</varname>.
</para>
</listitem>
</varlistentry>
@@ -912,9 +917,10 @@
</term>
<listitem>
<para>
- Seeks to the position <varname>TIME</varname> within the
- current song. If prefixed by '+' or '-', then the time
- is relative to the current playing position.
+ Seeks to the position <varname>TIME</varname> (in
+ seconds; fractions allowed) within the current song. If
+ prefixed by '+' or '-', then the time is relative to the
+ current playing position.
</para>
</listitem>
</varlistentry>
@@ -934,7 +940,7 @@
</variablelist>
</section>
- <section>
+ <section id="queue">
<title>The current playlist</title>
<variablelist>
@@ -1035,7 +1041,7 @@ OK
at <varname>START:END</varname> to <varname>TO</varname>
in the playlist.
<footnote id="range_since_0_15">
- <simpara>Ranges are supported since MPD 0.15</simpara>
+ <simpara>Ranges are supported since <application>MPD</application> 0.15</simpara>
</footnote>
</para>
</listitem>
@@ -1218,6 +1224,28 @@ OK
</listitem>
</varlistentry>
+ <varlistentry id="command_rangeid">
+ <term>
+ <cmdsynopsis>
+ <command>rangeid</command>
+ <arg choice="req"><replaceable>ID</replaceable></arg>
+ <arg choice="req"><replaceable>START:END</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ <footnote id="since_0_19"><simpara>Since <application>MPD</application>
+ 0.19</simpara></footnote> Specifies the portion of the
+ song that shall be played. <varname>START</varname> and
+ <varname>END</varname> are offsets in seconds
+ (fractional seconds allowed); both are optional.
+ Omitting both (i.e. sending just ":") means "remove the
+ range, play everything". A song that is currently
+ playing cannot be manipulated this way.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="command_shuffle">
<term>
<cmdsynopsis>
@@ -1263,10 +1291,48 @@ OK
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="command_addtagid">
+ <term>
+ <cmdsynopsis>
+ <command>addtagid</command>
+ <arg choice="req"><replaceable>SONGID</replaceable></arg>
+ <arg choice="req"><replaceable>TAG</replaceable></arg>
+ <arg choice="req"><replaceable>VALUE</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Adds a tag to the specified song. Editing song tags is
+ only possible for remote songs. This change is
+ volatile: it may be overwritten by tags received from
+ the server, and the data is gone when the song gets
+ removed from the queue.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_cleartagid">
+ <term>
+ <cmdsynopsis>
+ <command>cleartagid</command>
+ <arg choice="req"><replaceable>SONGID</replaceable></arg>
+ <arg choice="opt"><replaceable>TAG</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Removes tags from the specified song. If
+ <varname>TAG</varname> is not specified, then all tag
+ values will be removed. Editing song tags is only
+ possible for remote songs.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</section>
- <section>
+ <section id="playlist_files">
<title>Stored playlists</title>
<para>
@@ -1456,16 +1522,20 @@ OK
</variablelist>
</section>
- <section>
+ <section id="database">
<title>The music database</title>
<variablelist>
+
<varlistentry id="command_count">
<term>
<cmdsynopsis>
<command>count</command>
<arg choice="req"><replaceable>TAG</replaceable></arg>
<arg choice="req"><replaceable>NEEDLE</replaceable></arg>
+ <arg choice="opt"><replaceable>...</replaceable></arg>
+ <arg choice="opt">group</arg>
+ <arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
@@ -1473,8 +1543,15 @@ OK
Counts the number of songs and their total playtime in
the db matching <varname>TAG</varname> exactly.
</para>
+ <para>
+ The <parameter>group</parameter> keyword may be used to
+ group the results by a tag. The following prints
+ per-artist counts:
+ </para>
+ <programlisting>count group artist</programlisting>
</listitem>
</varlistentry>
+
<varlistentry id="command_find">
<term>
<cmdsynopsis>
@@ -1488,15 +1565,43 @@ OK
<para>
Finds songs in the db that are exactly
<varname>WHAT</varname>. <varname>TYPE</varname> can
- be any tag supported by MPD, or one of the three special
- parameters — <parameter>file</parameter> to search by
+ be any tag supported by <application>MPD</application>, or one of the special
+ parameters:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <parameter>any</parameter> checks all tag values
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <parameter>file</parameter> checks the full path
+ (relative to the music directory)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <parameter>base</parameter> restricts the search to
+ songs in the given directory (also relative to the
+ music directory)
+ </para>
+ </listitem>
- full path (relative to the music directory),
- <parameter>in</parameter> to restrict the search to
- songs in the given directory (also relative to the music
- directory) and
- <parameter>any</parameter> to match against all
- available tags. <varname>WHAT</varname> is what to find.
+ <listitem>
+ <para>
+ <parameter>modified-since</parameter> compares the
+ file's time stamp with the given value (ISO 8601 or
+ UNIX time stamp)
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ <varname>WHAT</varname> is what to find.
</para>
</listitem>
</varlistentry>
@@ -1517,27 +1622,43 @@ OK
</para>
</listitem>
</varlistentry>
+
<varlistentry id="command_list">
<term>
<cmdsynopsis>
<command>list</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
- <arg><replaceable>ARTIST</replaceable></arg>
+ <arg choice="opt"><replaceable>FILTERTYPE</replaceable></arg>
+ <arg choice="opt"><replaceable>FILTERWHAT</replaceable></arg>
+ <arg choice="opt"><replaceable>...</replaceable></arg>
+ <arg choice="opt">group</arg>
+ <arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
+ <arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
- Lists all tags of the specified type.
- <varname>TYPE</varname> can be any tag supported by MPD or
+ Lists unique tags values of the specified type.
+ <varname>TYPE</varname> can be any tag supported by
+ <application>MPD</application> or
<parameter>file</parameter>.
</para>
<para>
- <varname>ARTIST</varname> is an optional parameter when
- type is album, this specifies to list albums by an
- artist.
+ Additional arguments may specify a filter like the one
+ in the <link
+ linkend="command_find"><command>find</command>
+ command</link>.
+ </para>
+ <para>
+ The <parameter>group</parameter> keyword may be used
+ (repeatedly) to group the results by one or more tags.
+ The following example lists all album names,
+ grouped by their respective (album) artist:
</para>
+ <programlisting>list album group albumartist</programlisting>
</listitem>
</varlistentry>
+
<varlistentry id="command_listall">
<term>
<cmdsynopsis>
@@ -1550,6 +1671,14 @@ OK
Lists all songs and directories in
<varname>URI</varname>.
</para>
+ <para>
+ Do not use this command. Do not manage a client-side
+ copy of <application>MPD</application>'s database. That
+ is fragile and adds huge overhead. It will break with
+ large databases. Instead, query
+ <application>MPD</application> whenever you need
+ something.
+ </para>
</listitem>
</varlistentry>
<varlistentry id="command_listallinfo">
@@ -1565,6 +1694,40 @@ OK
returns metadata info in the same format as
<command>lsinfo</command>.
</para>
+ <para>
+ Do not use this command. Do not manage a client-side
+ copy of <application>MPD</application>'s database. That
+ is fragile and adds huge overhead. It will break with
+ large databases. Instead, query
+ <application>MPD</application> whenever you need
+ something.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="command_listfiles">
+ <term>
+ <cmdsynopsis>
+ <command>listfiles</command>
+ <arg><replaceable>URI</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Lists the contents of the directory
+ <varname>URI</varname>, including files are not
+ recognized by <application>MPD</application>.
+ <varname>URI</varname> can be a path relative to the
+ music directory or an URI understood by one of the
+ storage plugins. The response contains at least one
+ line for each directory entry with the prefix "file: "
+ or "directory: ", and may be followed by file attributes
+ such as "Last-Modified" and "size".
+ </para>
+ <para>
+ For example, "smb://SERVER" returns a list of all shares
+ on the given SMB/CIFS server; "nfs://servername/path"
+ obtains a directory listing from the NFS server.
+ </para>
</listitem>
</varlistentry>
<varlistentry id="command_lsinfo">
@@ -1585,6 +1748,10 @@ OK
deprecated; use "listplaylists" instead.
</para>
<para>
+ This command may be used to list metadata of remote
+ files (e.g. URI beginning with "http://" or "smb://").
+ </para>
+ <para>
Clients that are connected via UNIX domain socket may
use this command to read the tags of an arbitrary local
file (URI beginning with "file:///").
@@ -1606,6 +1773,10 @@ OK
"file:///foo/bar.ogg".
</para>
<para>
+ This command may be used to list metadata of remote
+ files (e.g. URI beginning with "http://" or "smb://").
+ </para>
+ <para>
The response consists of lines in the form "KEY: VALUE".
Comments with suspicious characters (e.g. newlines) are
ignored silently.
@@ -1722,22 +1893,131 @@ OK
</variablelist>
</section>
- <section>
+ <section id="mount">
+ <title>Mounts and neighbors</title>
+
+ <para>
+ A "storage" provides access to files in a directory tree. The
+ most basic storage plugin is the "local" storage plugin which
+ accesses the local file system, and there are plugins to
+ access NFS and SMB servers.
+ </para>
+
+ <para>
+ Multiple storages can be "mounted" together, similar to the
+ <application>mount</application> command on many operating
+ systems, but without cooperation from the kernel. No
+ superuser privileges are necessary, beause this mapping exists
+ only inside the <application>MPD</application> process
+ </para>
+
+ <variablelist>
+
+ <varlistentry id="command_mount">
+ <term>
+ <cmdsynopsis>
+ <command>mount</command>
+ <arg choice="req"><replaceable>PATH</replaceable></arg>
+ <arg choice="req"><replaceable>URI</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+
+ <listitem>
+ <para>
+ Mount the specified remote storage URI at the given
+ path. Example:
+ </para>
+
+ <programlisting>mount foo nfs://192.168.1.4/export/mp3</programlisting>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_umount">
+ <term>
+ <cmdsynopsis>
+ <command>unmount</command>
+ <arg choice="req"><replaceable>PATH</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+
+ <listitem>
+ <para>
+ Unmounts the specified path. Example:
+ </para>
+
+ <programlisting>unmount foo</programlisting>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_listmounts">
+ <term>
+ <cmdsynopsis>
+ <command>listmounts</command>
+ </cmdsynopsis>
+ </term>
+
+ <listitem>
+ <para>
+ Queries a list of all mounts. By default, this contains
+ just the configured <varname>music_directory</varname>.
+ Example:
+ </para>
+
+ <programlisting>listmounts
+mount:
+storage: /home/foo/music
+mount: foo
+storage: nfs://192.168.1.4/export/mp3
+OK
+</programlisting>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="command_listneighbors">
+ <term>
+ <cmdsynopsis>
+ <command>listneighbors</command>
+ </cmdsynopsis>
+ </term>
+
+ <listitem>
+ <para>
+ Queries a list of "neighbors" (e.g. accessible file
+ servers on the local net). Items on that list may be
+ used with the <link
+ linkend="command_mount"><command>mount</command></link>
+ command. Example:
+ </para>
+
+ <programlisting>listneighbors
+neighbor: smb://FOO
+name: FOO (Samba 4.1.11-Debian)
+OK
+</programlisting>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </section>
+
+ <section id="stickers">
<title>Stickers</title>
<para>
"Stickers"<footnoteref linkend="since_0_15"/> are pieces of
- information attached to existing MPD objects (e.g. song files,
+ information attached to existing
+ <application>MPD</application> objects (e.g. song files,
directories, albums). Clients can create arbitrary name/value
- pairs. MPD itself does not assume any special meaning in
- them.
+ pairs. <application>MPD</application> itself does not assume
+ any special meaning in them.
</para>
<para>
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).
+ song files (<application>MPD</application> has no write
+ access).
</para>
<para>
@@ -1842,7 +2122,7 @@ OK
</variablelist>
</section>
- <section>
+ <section id="connection_commands">
<title>Connection settings</title>
<variablelist>
@@ -1854,7 +2134,8 @@ OK
</term>
<listitem>
<para>
- Closes the connection to MPD. MPD will try to send the
+ Closes the connection to <application>MPD</application>.
+ <application>MPD</application> will try to send the
remaining output buffer before it actually closes the
connection, but that cannot be guaranteed. This command
will not generate a response.
@@ -1869,7 +2150,7 @@ OK
</term>
<listitem>
<para>
- Kills MPD.
+ Kills <application>MPD</application>.
</para>
</listitem>
</varlistentry>
@@ -1903,7 +2184,7 @@ OK
</variablelist>
</section>
- <section>
+ <section id="output_commands">
<title>Audio output devices</title>
<variablelist>
@@ -1957,12 +2238,38 @@ OK
<para>
Shows information about all outputs.
</para>
+ <screen>
+outputid: 0
+outputname: My ALSA Device
+outputenabled: 0
+OK
+ </screen>
+ <para>
+ Return information:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <varname>outputid</varname>: ID of the output. May change between executions
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <varname>outputname</varname>: Name of the output. It can be any.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <varname>outputenabled</varname>: Status of the output. 0 if disabled, 1 if enabled.
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
</varlistentry>
</variablelist>
</section>
- <section>
+ <section id="reflection_commands">
<title>Reflection</title>
<variablelist>
@@ -2078,7 +2385,7 @@ suffix: mpc</programlisting>
</variablelist>
</section>
- <section>
+ <section id="client_to_client">
<title>Client to client</title>
<para>
diff --git a/doc/user.xml b/doc/user.xml
index 6357267e4..a0bd861e8 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -4,7 +4,7 @@
<book>
<title>The Music Player Daemon - User's Manual</title>
- <chapter>
+ <chapter id="intro">
<title>Introduction</title>
<para>
@@ -13,10 +13,10 @@
</para>
<para>
- MPD (Music Player Daemon) is, as the name suggests, a server
- software allowing you to remotely play your music, handle
- playlists, deliver music (HTTP STREAMS with various
- sub-protocols) and organizze playlists.
+ <application>MPD</application> (Music Player Daemon) is, as the
+ name suggests, a server software allowing you to remotely play
+ your music, handle playlists, deliver music (HTTP streams with
+ various sub-protocols) and organizze playlists.
</para>
<para>
@@ -26,8 +26,8 @@
</para>
<para>
- MPD supports also Gapless playback, buffered audio output, and
- crossfading!
+ <application>MPD</application> supports also gapless playback,
+ buffered audio output, and crossfading!
</para>
<para>
@@ -37,38 +37,42 @@
</para>
</chapter>
- <chapter>
+ <chapter id="install">
<title>Installation</title>
<para>
We recommend that you use the software installation routines of
- your distribution to install MPD. Most operating systems have a
- MPD package, which is very easy to install.
+ your distribution to install <application>MPD</application>.
+ Most operating systems have a <application>MPD</application>
+ package, which is very easy to install.
</para>
- <section>
+ <section id="install_debian">
<title>Installing on Debian/Ubuntu</title>
<para>
- Install the package <filename>mpd</filename> via APT:
+ Install the package <application>MPD</application> via APT:
</para>
<programlisting>apt-get install mpd</programlisting>
<para>
- When installed this way, MPD by default looks for music in
- /var/lib/mpd/music/; this may not be correct. Look at your
- /etc/mpd.conf file...
+ When installed this way, <application>MPD</application> by
+ default looks for music in
+ <filename>/var/lib/mpd/music/</filename>; this may not be
+ correct. Look at your <filename>/etc/mpd.conf</filename>
+ file...
</para>
</section>
- <section>
+ <section id="install_source">
<title>Compiling from source</title>
<para>
Download the source tarball from <ulink
- url="http://www.musicpd.org/download.html">the MPD home
- page</ulink> and unpack it:
+ url="http://www.musicpd.org/download.html">the
+ <application>MPD</application> home page</ulink> and unpack
+ it:
</para>
<programlisting>tar xf mpd-version.tar.xz
@@ -80,6 +84,38 @@ cd mpd-version</programlisting>
</para>
<para>
+ For example, the following installs a fairly complete list of
+ build dependencies on Debian Wheezy:
+ </para>
+
+ <programlisting>
+apt-get install g++ \
+ libmad0-dev libmpg123-dev libid3tag0-dev \
+ libflac-dev libvorbis-dev libopus-dev \
+ libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
+ libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \
+ libmpcdec-dev libwavpack-dev libwildmidi-dev \
+ libsidplay2-dev libsidutils-dev libresid-builder-dev \
+ libavcodec-dev libavformat-dev \
+ libmp3lame-dev \
+ libsamplerate0-dev libsoxr-dev \
+ libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
+ libzzip-dev \
+ libcurl4-gnutls-dev libyajl-dev libexpat-dev \
+ libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
+ libpulse-dev libroar-dev libshout3-dev \
+ libmpdclient-dev \
+ libnfs-dev libsmbclient-dev \
+ libupnp-dev \
+ libavahi-client-dev \
+ libsqlite3-dev \
+ libsystemd-daemon-dev libwrap0-dev \
+ libcppunit-dev xmlto \
+ libboost-dev \
+ libglib2.0-dev libicu-dev
+ </programlisting>
+
+ <para>
Now configure the source tree:
</para>
@@ -100,71 +136,74 @@ cd mpd-version</programlisting>
<programlisting>make install</programlisting>
</section>
- <section>
+ <section id="systemd_socket">
<title><filename>systemd</filename> socket activation</title>
<para>
Using <filename>systemd</filename>, you can launch
- <filename>mpd</filename> on demand when the first client
- attempts to connect. Create two files in
- <filename>/etc/systemd/system/</filename>; first
- <filename>mpd.socket</filename>:
+ <application>MPD</application> on demand when the first client
+ attempts to connect.
</para>
- <programlisting>[Socket]
-ListenStream=/run/mpd.socket
-ListenStream=6600
-[Install]
-WantedBy=sockets.target</programlisting>
-
<para>
- Now create <filename>mpd.service</filename>:
+ <application>MPD</application> comes with two
+ <application>systemd</application> unit files: a "service"
+ unit and a "socket" unit. These will only be installed when
+ <application>MPD</application> was configured with
+ <parameter>--with-systemdsystemunitdir=/lib/systemd</parameter>.
</para>
- <programlisting>[Unit]
-Description=Music Player Daemon
-After=sound.target
-[Service]
-ExecStart=/usr/bin/mpd --stdout --no-daemon</programlisting>
-
<para>
- Start the socket:
+ To enable socket activation, type:
</para>
<programlisting>systemctl enable mpd.socket
systemctl start mpd.socket</programlisting>
<para>
- In this configuration, <filename>mpd</filename> will ignore
- the <varname>bind_to_address</varname> and
+ In this configuration, <application>MPD</application> will
+ ignore the <varname>bind_to_address</varname> and
<varname>port</varname> settings.
</para>
</section>
</chapter>
- <chapter>
+ <chapter id="config">
<title>Configuration</title>
- <section>
+ <section id="config_music_directory">
<title>Configuring the music directory</title>
<para>
When you play local files, you should organize them within a
directory called the "music directory". This is configured in
- MPD with the <varname>music_directory</varname> setting.
+ <application>MPD</application> with the
+ <varname>music_directory</varname> setting.
+ </para>
+
+ <para>
+ By default, <application>MPD</application> follows symbolic
+ links in the music directory. This behavior can be switched
+ off: <varname>follow_outside_symlinks</varname> controls
+ whether <application>MPD</application> follows links pointing
+ to files outside of the music directory, and
+ <varname>follow_inside_symlinks</varname> lets you disable
+ symlinks to files inside the music directory.
</para>
<para>
- By default, MPD follows symbolic links in the music directory.
- This behavior can be switched off:
- <varname>follow_outside_symlinks</varname> controls whether
- MPD follows links pointing to files outside of the music
- directory, and <varname>follow_inside_symlinks</varname> lets
- you disable symlinks to files inside the music directory.
+ Instead of using local files, you can use <link
+ linkend="storage_plugins">storage plugins</link> to access
+ files on a remote file server. For example, to use music from
+ the SMB/CIFS server "myfileserver" on the share called
+ "Music", configure the music directory
+ "<parameter>smb://myfileserver/Music</parameter>". For a
+ recipe, read the <link linkend="satellite">Satellite
+ MPD</link> section.
</para>
</section>
- <section>
+ <section id="config_database_plugins">
<title>Configuring database plugins</title>
<para>
@@ -209,9 +248,65 @@ systemctl start mpd.socket</programlisting>
</tbody>
</tgroup>
</informaltable>
+
+ <para>
+ More information can be found in the <link
+ linkend="database_plugins">database plugin reference</link>.
+ </para>
</section>
- <section>
+ <section id="config_neighbor_plugins">
+ <title>Configuring neighbor plugins</title>
+
+ <para>
+ All neighbor plugins are disabled by default to avoid unwanted
+ overhead. To enable (and configure) a plugin, add a
+ <varname>neighbor</varname> block to
+ <filename>mpd.conf</filename>:
+ </para>
+
+ <programlisting>neighbors {
+ plugin "smbclient"
+}
+ </programlisting>
+
+ <para>
+ The following table lists the <varname>neighbor</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>
+
+ <para>
+ More information can be found in the <link
+ linkend="neighbor_plugins">neighbor plugin reference</link>.
+ </para>
+ </section>
+
+ <section id="config_input_plugins">
<title>Configuring input plugins</title>
<para>
@@ -265,9 +360,14 @@ systemctl start mpd.socket</programlisting>
</tbody>
</tgroup>
</informaltable>
+
+ <para>
+ More information can be found in the <link
+ linkend="input_plugins">input plugin reference</link>.
+ </para>
</section>
- <section>
+ <section id="config_decoder_plugins">
<title>Configuring decoder plugins</title>
<para>
@@ -321,27 +421,36 @@ systemctl start mpd.socket</programlisting>
</tbody>
</tgroup>
</informaltable>
+
+ <para>
+ More information can be found in the <link
+ linkend="decoder_plugins">decoder plugin reference</link>.
+ </para>
</section>
- <section>
+ <section id="config_encoder_plugins">
<title>Configuring encoder plugins</title>
<para>
- Encoders are used by some of the output plugins (such as
- <varname>shout</varname>). The encoder settings are included
- in the <varname>audio_output</varname> section.
+ Encoders are used by some of the output plugins (such as <link
+ linkend="shout_output"><varname>shout</varname></link>). The
+ encoder settings are included in the
+ <varname>audio_output</varname> section. More information can
+ be found in the <link linkend="encoder_plugins">encoder plugin
+ reference</link>.
</para>
</section>
- <section>
+ <section id="config_audio_outputs">
<title>Configuring audio outputs</title>
<para>
Audio outputs are devices which actually play the audio chunks
- produced by MPD. You can configure any number of audio output
- devices, but there must be at least one. If none is
- configured, MPD attempts to auto-detect. Usually, this works
- quite well with ALSA, OSS and on Mac OS X.
+ produced by <application>MPD</application>. You can configure
+ any number of audio output devices, but there must be at least
+ one. If none is configured, <application>MPD</application>
+ attempts to auto-detect. Usually, this works quite well with
+ ALSA, OSS and on Mac OS X.
</para>
<para>
@@ -393,7 +502,7 @@ systemctl start mpd.socket</programlisting>
a name registered in the PULSE server.
</entry>
</row>
- <row>
+ <row id="ao_format">
<entry>
<varname>format</varname>
</entry>
@@ -417,8 +526,6 @@ systemctl start mpd.socket</programlisting>
(signed 8 bit integer samples),
<varname>16</varname>, <varname>24</varname> (signed
24 bit integer samples padded to 32 bit),
- <varname>24_3</varname> (signed 24 bit integer
- samples, no padding, 3 bytes per sample),
<varname>32</varname> (signed 32 bit integer
samples), <varname>f</varname> (32 bit floating
point, -1.0 to 1.0).
@@ -432,8 +539,8 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Specifies whether this audio output is enabled when
- MPD is started. By default, all audio outputs are
- enabled.
+ <application>MPD</application> is started. By
+ default, all audio outputs are enabled.
</entry>
</row>
<row>
@@ -442,10 +549,12 @@ systemctl start mpd.socket</programlisting>
<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.
+ If set to <parameter>no</parameter>, then
+ <application>MPD</application> will not send tags to
+ this output. This is only useful for output plugins
+ that can receive tags, for example the <link
+ linkend="httpd_output"><varname>httpd</varname></link>
+ output plugin.
</entry>
</row>
<row>
@@ -454,10 +563,12 @@ systemctl start mpd.socket</programlisting>
<parameter>yes|no</parameter>
</entry>
<entry>
- If set to "yes", then MPD attempts to keep this audio
- output always open. This may be useful for streaming
- servers, when you don't want to disconnect all
- listeners even when playback is accidentally stopped.
+ If set to <parameter>yes</parameter>, then
+ <application>MPD</application> attempts to keep this
+ audio output always open. This may be useful for
+ streaming servers, when you don't want to disconnect
+ all listeners even when playback is accidentally
+ stopped.
</entry>
</row>
<row>
@@ -467,10 +578,14 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Specifies which mixer should be used for this audio
- output: the hardware mixer (available for ALSA, OSS
- and PulseAudio), the software mixer or no mixer
- ("none"). By default, the hardware mixer is used for
- devices which support it, and none for the others.
+ output: the hardware mixer (available for <link
+ linkend="alsa_output">ALSA</link>, <link
+ linkend="oss_output">OSS</link> and <link
+ linkend="pulse_output">PulseAudio</link>), the
+ software mixer or no mixer
+ (<parameter>none</parameter>). By default, the
+ hardware mixer is used for devices which support it,
+ and none for the others.
</entry>
</row>
<row>
@@ -480,10 +595,11 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Specifies how replay gain is applied. The default is
- "software", which uses an internal software volume
- control. "mixer" uses the configured (hardware) mixer
- control. "none" disables replay gain on this audio
- output.
+ <parameter>software</parameter>, which uses an
+ internal software volume control.
+ <parameter>mixer</parameter> uses the configured
+ (hardware) mixer control. <parameter>none</parameter>
+ disables replay gain on this audio output.
</entry>
</row>
</tbody>
@@ -491,7 +607,7 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="config_filters">
<title>Configuring filters</title>
<para>
@@ -548,12 +664,13 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="config_playlist_plugins">
<title>Configuring playlist plugins</title>
<para>
Playlist plugins are used to load remote playlists. This is
- not related to MPD's playlist directory.
+ not related to <application>MPD</application>'s playlist
+ directory.
</para>
<para>
@@ -608,18 +725,484 @@ systemctl start mpd.socket</programlisting>
</tbody>
</tgroup>
</informaltable>
+
+ <para>
+ More information can be found in the <link
+ linkend="playlist_plugins">playlist plugin reference</link>.
+ </para>
+ </section>
+
+ <section id="config_audio_format">
+ <title>Audio Format Settings</title>
+
+ <section id="config_global_audio_format">
+ <title>Global Audio Format</title>
+
+ <para>
+ The setting <varname>audio_output_format</varname> forces
+ <application>MPD</application> to use one audio format for
+ all outputs. Doing that is usually not a good idea. The
+ values are the same as in <link
+ linkend="ao_format"><varname>format</varname> in the <link
+ linkend="config_audio_outputs"><varname>audio_output</varname></link>
+ section</link>.
+ </para>
+ </section>
+
+ <section>
+ <title>Resampler</title>
+
+ <para>
+ Sometimes, music needs to be resampled before it can be
+ played; for example, CDs use a sample rate of 44,100 Hz
+ while many cheap audio chips can only handle 48,000 Hz.
+ Resampling reduces the quality and consumes a lot of CPU.
+ There are different options, some of them optimized for high
+ quality and others for low CPU usage, but you can't have
+ both at the same time. Often, the resampler is the
+ component that is responsible for most of
+ <application>MPD</application>'s CPU usage. Since
+ <application>MPD</application> comes with high quality
+ defaults, it may appear that <application>MPD</application>
+ consumes more CPU than other software.
+ </para>
+
+ <para>
+ The following resamplers are available (if enabled at
+ compile time):
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <ulink
+ url="http://www.mega-nerd.com/SRC/"><application>libsamplerate</application></ulink>
+ a.k.a. Secret Rabbit Code (SRC).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <ulink
+ url="http://sourceforge.net/projects/soxr/"><application>libsoxr</application></ulink>,
+ the SoX Resampler library
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ internal: low CPU usage, but very poor quality. This is
+ the fallback if <application>MPD</application> was
+ compiled without an external resampler.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The setting <varname>samplerate_converter</varname> controls
+ how <application>MPD</application> shall resample music.
+ Possible values:
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>
+ Value
+ </entry>
+ <entry>
+ Description
+ </entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ "<parameter>internal</parameter>"
+ </entry>
+ <entry>
+ The internal resampler. Low CPU usage, but very
+ poor quality.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>soxr very high</parameter>"
+ </entry>
+ <entry>
+ Use <application>libsoxr</application> with "Very
+ High Quality" setting.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>soxr high</parameter>" or
+ "<parameter>soxr</parameter>"
+ </entry>
+ <entry>
+ Use <application>libsoxr</application> with "High
+ Quality" setting.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>soxr medium</parameter>"
+ </entry>
+ <entry>
+ Use <application>libsoxr</application> with "Medium
+ Quality" setting.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>soxr low</parameter>"
+ </entry>
+ <entry>
+ Use <application>libsoxr</application> with "Low
+ Quality" setting.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>soxr quick</parameter>"
+ </entry>
+ <entry>
+ Use <application>libsoxr</application> with "Quick"
+ setting.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Best Sinc Interpolator</parameter>" or
+ "<parameter>0</parameter>"
+ </entry>
+ <entry>
+ <application>libsamplerate</application>: Band
+ limited sinc interpolation, best quality, 97dB SNR,
+ 96% BW.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Medium Sinc Interpolator</parameter>" or
+ "<parameter>1</parameter>"
+ </entry>
+ <entry>
+ <application>libsamplerate</application>: Band
+ limited sinc interpolation, medium quality, 97dB
+ SNR, 90% BW.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Fastest Sinc Interpolator</parameter>" or
+ "<parameter>2</parameter>"
+ </entry>
+ <entry>
+ <application>libsamplerate</application>: Band
+ limited sinc interpolation, fastest, 97dB SNR, 80%
+ BW.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>ZOH Sinc Interpolator</parameter>" or
+ "<parameter>3</parameter>"
+ </entry>
+ <entry>
+ <application>libsamplerate</application>: Zero order
+ hold interpolator, very fast, very poor quality with
+ audible distortions.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ "<parameter>Linear Interpolator</parameter>" or
+ "<parameter>4</parameter>"
+ </entry>
+ <entry>
+ <application>libsamplerate</application>: Linear
+ interpolator, very fast, poor quality.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+ </section>
+
+ <section id="config_other">
+ <title>Other Settings</title>
+
+ <section>
+ <title>The State File</title>
+
+ <para>
+ The <emphasis>state file</emphasis> is a file where
+ <application>MPD</application> saves and restores its state
+ (play queue, playback position etc.) to keep it persistent
+ across restarts and reboots. It is an optional setting.
+ </para>
+
+ <para>
+ <application>MPD</application> will attempt to load the
+ state file during startup, and will save it when shutting
+ down the daemon. Additionally, the state file is refreshed
+ every two minutes (after each state change).
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>state_file</varname>
+ <parameter>PATH</parameter>
+ </entry>
+ <entry>
+ Specify the state file location. The parent
+ directory must be writable by the
+ <application>MPD</application> user
+ (<parameter>+wx</parameter>).
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>state_file_interval</varname>
+ <parameter>SECONDS</parameter>
+ </entry>
+ <entry>
+ Auto-save the state file this number of seconds
+ after each state change. Defaults to
+ <parameter>120</parameter> (2 minutes).
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title>Resource Limitations</title>
+
+ <para>
+ These settings are various limitations to prevent
+ <application>MPD</application> from using too many
+ resources (denial of service).
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+
+ <row>
+ <entry>
+ <varname>connection_timeout</varname>
+ <parameter>SECONDS</parameter>
+ </entry>
+ <entry>
+ If a client does not send any new data in this time
+ period, the connection is closed. Clients waiting
+ in "idle" mode are excluded from this. Default is
+ <parameter>60</parameter>.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>max_connections</varname>
+ <parameter>NUMBER</parameter>
+ </entry>
+ <entry>
+ This specifies the maximum number of clients that
+ can be connected to <application>MPD</application>
+ at the same time. Default is
+ <parameter>5</parameter>.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>max_playlist_length</varname>
+ <parameter>NUMBER</parameter>
+ </entry>
+ <entry>
+ The maximum number of songs that can be in the
+ playlist. Default is <parameter>16384</parameter>.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>max_command_list_size</varname>
+ <parameter>KBYTES</parameter>
+ </entry>
+ <entry>
+ The maximum size a command list. Default is
+ <parameter>2048</parameter> (2 MiB).
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>max_output_buffer_size</varname>
+ <parameter>KBYTES</parameter>
+ </entry>
+ <entry>
+ The maximum size of the output buffer to a client
+ (maximum response size). Default is
+ <parameter>8192</parameter> (8 MiB).
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title>Buffer Settings</title>
+
+ <para>
+ Do not change these unless you know what you are doing.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+
+ <row>
+ <entry>
+ <varname>audio_buffer_size</varname>
+ <parameter>KBYTES</parameter>
+ </entry>
+ <entry>
+ Adjust the size of the internal audio buffer.
+ Default is <parameter>4096</parameter> (4 MiB).
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>buffer_before_play</varname>
+ <parameter>PERCENT</parameter>
+ </entry>
+ <entry>
+ Control the percentage of the buffer which is filled
+ before beginning to play. Increasing this reduces
+ the chance of audio file skipping, at the cost of
+ increased time prior to audio playback. Default is
+ <parameter>10%</parameter>.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+ </section>
+ </chapter>
+
+ <chapter id="advanced_config">
+ <title>Advanced configuration</title>
+
+ <section id="satellite">
+ <title>Satellite setup</title>
+
+ <para>
+ <application>MPD</application> runs well on weak machines such
+ as the <ulink url="http://www.raspberrypi.org/">Raspberry
+ Pi</ulink>. However, such hardware tends to not have storage
+ big enough to hold a music collection. Mounting music from a
+ file server can be very slow, especially when updating the
+ database.
+ </para>
+
+ <para>
+ One approach for optimization is running
+ <application>MPD</application> on the file server, which not
+ only exports raw files, but also provides access to a readily
+ scanned database. Example configuration:
+ </para>
+
+ <programlisting>music_directory "nfs://fileserver.local/srv/mp3"
+#music_directory "smb://fileserver.local/mp3"
+
+database {
+ plugin "proxy"
+ host "fileserver.local"
+}
+ </programlisting>
+
+ <para>
+ The <link
+ linkend="config_music_directory"><varname>music_directory</varname></link>
+ setting tells <application>MPD</application> to read files
+ from the given NFS server. It does this by connecting to the
+ server from userspace. This does not actually mount the file
+ server into the kernel's virtual file system, and thus
+ requires no kernel cooperation and no special privileges. It
+ does not even require a kernel with NFS support, only the
+ <link linkend="nfs_storage"><filename>nfs</filename></link>
+ storage plugin (using the <filename>libnfs</filename>
+ userspace library). The same can be done with SMB/CIFS using
+ the <link
+ linkend="smbclient_storage"><filename>smbclient</filename></link>
+ storage plugin (using <filename>libsmbclient</filename>).
+ </para>
+
+ <para>
+ The <link
+ linkend="config_database_plugins"><varname>database</varname></link>
+ setting tells <application>MPD</application> to pass all
+ database queries on to the <application>MPD</application>
+ instance running on the file server (using the <link
+ linkend="proxy_database"><filename>proxy</filename></link>
+ plugin).
+ </para>
</section>
</chapter>
- <chapter>
- <title>Using MPD</title>
+ <chapter id="use">
+ <title>Using <application>MPD</application></title>
- <section>
+ <section id="client">
<title>The client</title>
<para>
- After you have installed, configured and started MPD, you
- choose a client to control the playback.
+ After you have installed, configured and started
+ <application>MPD</application>, you choose a client to control
+ the playback.
</para>
<para>
@@ -630,21 +1213,23 @@ systemctl start mpd.socket</programlisting>
</para>
<para>
- The <ulink url="http://www.musicpd.org/clients/">MPD
+ The <ulink
+ url="http://www.musicpd.org/clients/"><application>MPD</application>
Wiki</ulink> contains an extensive list of clients to choose
from.
</para>
</section>
- <section>
+ <section id="music_directory_and_database">
<title>The music directory and the database</title>
<para>
The "music directory" is where you store your music files.
- MPD stores all relevant meta information about all songs in
- its "database". Whenever you add, modify or remove songs in
- the music directory, you have to update the database, for
- example with <filename>mpc</filename>:
+ <application>MPD</application> stores all relevant meta
+ information about all songs in its "database". Whenever you
+ add, modify or remove songs in the music directory, you have
+ to update the database, for example with
+ <filename>mpc</filename>:
</para>
<programlisting>mpc update</programlisting>
@@ -661,22 +1246,192 @@ systemctl start mpd.socket</programlisting>
</para>
</section>
- <section>
+ <section id="queue">
<title>The queue</title>
<para>
The queue (sometimes called "current playlist") is a list of
- songs to be played by MPD. To play a song, add it to the
- queue and start playback. Most clients offer an interface to
- edit the queue.
+ songs to be played by <application>MPD</application>. To play
+ a song, add it to the queue and start playback. Most clients
+ offer an interface to edit the queue.
</para>
</section>
</chapter>
- <chapter>
+ <chapter id="advanced_usage">
+ <title>Advanced usage</title>
+
+ <section id="bit_perfect">
+ <title>Bit-perfect playback</title>
+
+ <para>
+ "Bit-perfect playback" is a phrase used by audiophiles to
+ describe a setup that plays back digital music as-is, without
+ applying any modifications such as resampling, format
+ conversion or software volume. Naturally, this implies a
+ lossless codec.
+ </para>
+
+ <para>
+ By default, <application>MPD</application> attempts to do
+ bit-perfect playback, unless you tell it not to. Precondition
+ is a sound chip that supports the audio format of your music
+ files. If the audio format is not supported,
+ <application>MPD</application> attempts to fall back to the
+ nearest supported audio format, trying to lose as little
+ quality as possible.
+ </para>
+
+ <para>
+ To verify if <application>MPD</application> converts the audio
+ format, enable verbose logging, and watch for these lines:
+ </para>
+
+ <programlisting>decoder: audio_format=44100:24:2, seekable=true
+output: opened plugin=alsa name="An ALSA output" audio_format=44100:16:2
+output: converting from 44100:24:2</programlisting>
+
+ <para>
+ This example shows that a 24 bit file is being played, but the
+ sond chip cannot play 24 bit. It falls back to 16 bit,
+ discarding 8 bit.
+ </para>
+
+ <para>
+ However, this does not yet prove bit-perfect playback;
+ <application>ALSA</application> may be fooling
+ <application>MPD</application> that the audio format is
+ supported. To verify the format really being sent to the
+ physical sound chip, try:
+ </para>
+
+ <programlisting>cat /proc/asound/card*/pcm*p/sub*/hw_params
+access: RW_INTERLEAVED
+format: S16_LE
+subformat: STD
+channels: 2
+rate: 44100 (44100/1)
+period_size: 4096
+buffer_size: 16384</programlisting>
+
+ <para>
+ Obey the "format" row, which indicates that the current
+ playback format is 16 bit (signed 16 bit integer, little
+ endian).
+ </para>
+
+ <para>
+ Check list for bit-perfect playback:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Use the <link linkend="alsa_output">ALSA</link> output
+ plugin.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Disable sound processing inside
+ <application>ALSA</application> by configuring a
+ "hardware" device (<parameter>hw:0,0</parameter> or
+ similar).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Don't use software volume (setting <link
+ linkend="config_audio_outputs"><varname>mixer_type</varname></link>).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Don't force <application>MPD</application> to use a
+ specific audio format (settings <link
+ linkend="config_audio_outputs"><varname>format</varname></link>,
+ <link
+ linkend="config_global_audio_format"><varname>audio_output_format</varname></link>).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Verify that you are really doing bit-perfect playback
+ using <application>MPD</application>'s verbose log and
+ <filename>/proc/asound/card*/pcm*p/sub*/hw_params</filename>.
+ Some DACs can also indicate the audio format.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </section>
+
+ <section id="dsd">
+ <title>Direct Stream Digital (DSD)</title>
+
+ <para>
+ DSD (<ulink
+ url="https://en.wikipedia.org/wiki/Direct_Stream_Digital">Direct
+ Stream Digital</ulink>) is a digital format that stores audio
+ as a sequence of single-bit values at a very high sampling
+ rate.
+ </para>
+
+ <para>
+ <application>MPD</application> understands the file formats
+ <link linkend="dsdiff_decoder"><filename>dff</filename></link>
+ and <link
+ linkend="dsf_decoder"><filename>dsf</filename></link>. There
+ are three ways to play back DSD:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Native DSD playback. Requires
+ <application>ALSA</application> 1.0.27.1 or later, a sound
+ driver/chip that supports DSD and of course a DAC that
+ supports DSD.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DoP (DSD over PCM) playback. This wraps DSD inside fake
+ 24 bit PCM according to the <ulink
+ url="http://dsd-guide.com/dop-open-standard">DoP
+ standard</ulink>. Requires a DAC that supports DSD. No
+ support from ALSA and the sound chip required (except for
+ <link linkend="bit_perfect">bit-perfect</link> 24 bit PCM
+ support).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Convert DSD to PCM on-the-fly.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ Native DSD playback is used automatically if available. DoP
+ is only used if enabled explicitly using the <link
+ linkend="alsa_output"><varname>dop</varname></link> option,
+ because there is no way for <application>MPD</application> to
+ find out whether the DAC supports it. DSD to PCM conversion
+ is the fallback if DSD cannot be used directly.
+ </para>
+ </section>
+ </chapter>
+
+ <chapter id="plugin_reference">
<title>Plugin reference</title>
- <section>
+ <section id="database_plugins">
<title>Database plugins</title>
<section>
@@ -704,61 +1459,46 @@ systemctl start mpd.socket</programlisting>
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>. This is useful
- when you run mount the music directory via NFS/SMB, and the
- file server already runs a MPD instance. Only the file
- server needs to update the database.
- </para>
-
- <informaltable>
- <tgroup cols="2">
- <thead>
- <row>
- <entry>Setting</entry>
- <entry>Description</entry>
- </row>
- </thead>
- <tbody>
<row>
<entry>
- <varname>host</varname>
+ <varname>cache_directory</varname>
</entry>
<entry>
- The host name of the "master" MPD instance.
+ The path of the cache directory for additional
+ storages mounted at runtime. This setting is
+ necessary for the <command>mount</command> protocol
+ command.
</entry>
</row>
+
<row>
<entry>
- <varname>port</varname>
+ <varname>compress</varname>
+ <parameter>yes|no</parameter>
</entry>
<entry>
- The port number of the "master" MPD instance.
+ Compress the database file using
+ <filename>gzip</filename>? Enabled by default (if
+ built with <filename>zlib</filename>).
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
- </section>
-
- <section>
- <title>Input plugins</title>
- <section>
- <title><varname>curl</varname></title>
+ <section id="proxy_database">
+ <title><varname>proxy</varname></title>
<para>
- Opens remote files or streams over HTTP.
+ Provides access to the database of another
+ <application>MPD</application> instance using
+ <filename>libmpdclient</filename>. This is useful when you
+ run mount the music directory via NFS/SMB, and the file
+ server already runs a <application>MPD</application>
+ instance. Only the file server needs to update the
+ database.
</para>
<informaltable>
@@ -772,19 +1512,20 @@ systemctl start mpd.socket</programlisting>
<tbody>
<row>
<entry>
- <varname>proxy</varname>
+ <varname>host</varname>
</entry>
<entry>
- Sets the address of the HTTP proxy server.
+ The host name of the "master"
+ <application>MPD</application> instance.
</entry>
</row>
<row>
<entry>
- <varname>proxy_user</varname>,
- <varname>proxy_password</varname>
+ <varname>port</varname>
</entry>
<entry>
- Configures proxy authentication.
+ The port number of the "master"
+ <application>MPD</application> instance.
</entry>
</row>
</tbody>
@@ -793,18 +1534,111 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
- <title><varname>file</varname></title>
+ <title><varname>upnp</varname></title>
<para>
- Opens local files.
+ Provides access to UPnP media servers.
</para>
</section>
+ </section>
+
+ <section id="storage_plugins">
+ <title>Storage plugins</title>
<section>
- <title><varname>mms</varname></title>
+ <title><varname>local</varname></title>
<para>
- Plays streams with the MMS protocol.
+ The default plugin which gives
+ <application>MPD</application> access to local files. It is
+ used when <varname>music_directory</varname> refers to a
+ local directory.
+ </para>
+ </section>
+
+ <section id="smbclient_storage">
+ <title><varname>smbclient</varname></title>
+
+ <para>
+ Load music files from a SMB/CIFS server. It used used when
+ <varname>music_directory</varname> contains a
+ <parameter>smb://</parameter> URI, for example
+ "<parameter>smb://myfileserver/Music</parameter>".
+ </para>
+ </section>
+
+ <section id="nfs_storage">
+ <title><varname>nfs</varname></title>
+
+ <para>
+ Load music files from a NFS server. It used used when
+ <varname>music_directory</varname> contains a
+ <parameter>nfs://</parameter> URI according to <ulink
+ url="http://tools.ietf.org/html/rfc2224">RFC2224</ulink>,
+ for example "<parameter>nfs://servername/path</parameter>".
+ </para>
+
+ <para>
+ This plugin uses <ulink
+ url="https://github.com/sahlberg/libnfs"><filename>libnfs</filename></ulink>,
+ which supports only NFS version 3. Since
+ <application>MPD</application> is not allowed to bind to
+ "privileged ports", the NFS server needs to enable the
+ "insecure" setting; example
+ <filename>/etc/exports</filename>:
+ </para>
+
+ <programlisting>/srv/mp3 192.168.1.55(ro,insecure)</programlisting>
+
+ <para>
+ Don't fear: "insecure" does not mean that your NFS server is
+ insecure. A few decades ago, people thought the concept of
+ "privileged ports" would make network services "secure",
+ which was a fallacy. The absence of this obsolete
+ "security" measure means little.
+ </para>
+ </section>
+ </section>
+
+ <section id="neighbor_plugins">
+ <title>Neighbor plugins</title>
+
+ <section id="smbclient_neighbor">
+ <title><varname>smbclient</varname></title>
+
+ <para>
+ Provides a list of SMB/CIFS servers on the local network.
+ </para>
+ </section>
+
+ <section id="upnp_neighbor">
+ <title><varname>upnp</varname></title>
+
+ <para>
+ Provides a list of UPnP servers on the local network.
+ </para>
+ </section>
+ </section>
+
+ <section id="input_plugins">
+ <title>Input plugins</title>
+
+ <section>
+ <title><varname>alsa</varname></title>
+
+ <para>
+ Allows <application>MPD</application> on Linux to play audio
+ directly from a soundcard using the scheme
+ <filename>alsa://</filename>. Audio is formatted as 44.1 kHz
+ 16-bit stereo (CD format). Examples:
+ </para>
+
+ <para>
+ <filename>mpc add alsa://</filename> plays audio from device hw:0,0
+ </para>
+ <para>
+ <filename>mpc add alsa://hw:1,0</filename> plays audio from device
+ hw:1,0
</para>
</section>
@@ -833,9 +1667,72 @@ systemctl start mpd.socket</programlisting>
<parameter>little_endian|big_endian</parameter>
</entry>
<entry>
- If the CD drive does not specify a byte order, MPD
- assumes it is the CPU's native byte order. This
- setting allows overriding this.
+ If the CD drive does not specify a byte order,
+ <application>MPD</application> assumes it is the
+ CPU's native byte order. This setting allows
+ overriding this.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title><varname>curl</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>
+ <row>
+ <entry>
+ <varname>proxy_user</varname>,
+ <varname>proxy_password</varname>
+ </entry>
+ <entry>
+ Configures proxy authentication.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>verify_peer</varname>
+ <parameter>yes|no</parameter>
+ </entry>
+ <entry>
+ Verify the peer's SSL certificate? <ulink
+ url="http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html">More
+ information</ulink>.
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <varname>verify_host</varname>
+ <parameter>yes|no</parameter>
+ </entry>
+ <entry>
+ Verify the certificate's name against host? <ulink
+ url="http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html">More
+ information</ulink>.
</entry>
</row>
</tbody>
@@ -902,12 +1799,72 @@ systemctl start mpd.socket</programlisting>
</tgroup>
</informaltable>
</section>
+
+ <section>
+ <title><varname>file</varname></title>
+
+ <para>
+ Opens local files.
+ </para>
+ </section>
+
+ <section>
+ <title><varname>mms</varname></title>
+
+ <para>
+ Plays streams with the MMS protocol.
+ </para>
+ </section>
+
+ <section>
+ <title><varname>nfs</varname></title>
+
+ <para>
+ Allows <application>MPD</application> to access files on
+ NFSv3 servers without actually mounting them (i.e. in
+ userspace, without help from the kernel's VFS layer). All
+ URIs with the <filename>nfs://</filename> scheme are used
+ according to <ulink
+ url="http://tools.ietf.org/html/rfc2224">RFC2224</ulink>.
+ Example:
+ </para>
+
+ <para>
+ <filename>mpc add nfs://servername/path/filename.ogg</filename>
+ </para>
+
+ <para>
+ Note that this usually requires enabling the "insecure" flag
+ in the server's <filename>/etc/exports</filename> file,
+ because <application>MPD</application> cannot bind to
+ so-called "privileged" ports. Don't fear: this will not
+ make your file server insecure; the flag was named in a time
+ long ago when privileged ports were thought to be meaningful
+ for security. By today's standards, NFSv3 is not secure at
+ all, and if you believe it is, you're already doomed.
+ </para>
+ </section>
+
+ <section>
+ <title><varname>smbclient</varname></title>
+
+ <para>
+ Allows <application>MPD</application> to access files on
+ SMB/CIFS servers (e.g. Samba or Microsoft Windows). All
+ URIs with the <filename>smb://</filename> scheme are used.
+ Example:
+ </para>
+
+ <para>
+ <filename>mpc add smb://servername/sharename/filename.ogg</filename>
+ </para>
+ </section>
</section>
- <section>
+ <section id="decoder_plugins">
<title>Decoder plugins</title>
- <section>
+ <section id="dsdiff_decoder">
<title><varname>dsdiff</varname></title>
<para>
@@ -930,7 +1887,7 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Decode the least significant bit first. Default is
- "no".
+ <parameter>no</parameter>.
</entry>
</row>
</tbody>
@@ -938,7 +1895,7 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="dsf_decoder">
<title><varname>dsf</varname></title>
<para>
@@ -951,7 +1908,8 @@ systemctl start mpd.socket</programlisting>
<title><varname>fluidsynth</varname></title>
<para>
- MIDI decoder based on libfluidsynth.
+ MIDI decoder based on <ulink
+ url="http://www.fluidsynth.org/"><application>FluidSynth</application></ulink>.
</para>
<informaltable>
@@ -991,7 +1949,8 @@ systemctl start mpd.socket</programlisting>
<title><varname>mikmod</varname></title>
<para>
- Module player based on MikMod.
+ Module player based on <ulink
+ url="http://mikmod.sourceforge.net/"><application>MikMod</application></ulink>.
</para>
<informaltable>
@@ -1031,7 +1990,7 @@ systemctl start mpd.socket</programlisting>
<title><varname>modplug</varname></title>
<para>
- Module player based on MODPlug.
+ Module player based on <application>MODPlug</application>.
</para>
<informaltable>
@@ -1062,7 +2021,8 @@ systemctl start mpd.socket</programlisting>
<title><varname>wildmidi</varname></title>
<para>
- MIDI decoder based on libwildmidi.
+ MIDI decoder based on <ulink
+ url="http://www.mindwerks.net/projects/wildmidi/"><application>libwildmidi</application></ulink>.
</para>
<informaltable>
@@ -1090,14 +2050,15 @@ systemctl start mpd.socket</programlisting>
</section>
</section>
- <section>
+ <section id="encoder_plugins">
<title>Encoder plugins</title>
<section>
<title><varname>flac</varname></title>
<para>
- Encodes into FLAC (lossless).
+ Encodes into <ulink
+ url="https://xiph.org/flac/">FLAC</ulink> (lossless).
</para>
<informaltable>
@@ -1128,7 +2089,9 @@ systemctl start mpd.socket</programlisting>
<title><varname>lame</varname></title>
<para>
- Encodes into MP3 using the LAME library.
+ Encodes into MP3 using the <ulink
+ url="http://lame.sourceforge.net/"><application>LAME</application></ulink>
+ library.
</para>
<informaltable>
@@ -1173,10 +2136,42 @@ systemctl start mpd.socket</programlisting>
</section>
<section>
+ <title><varname>shine</varname></title>
+
+ <para>
+ Encodes into MP3 using the <ulink
+ url="https://github.com/savonet/shine"><application>Shine</application></ulink>
+ library.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>bitrate</varname>
+ </entry>
+ <entry>
+ Sets the bit rate in kilobit per second.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
<title><varname>twolame</varname></title>
<para>
- Encodes into MP2 using the <filename>twolame</filename>
+ Encodes into MP2 using the <ulink
+ url="http://www.twolame.org/"><application>TwoLAME</application></ulink>
library.
</para>
@@ -1213,11 +2208,12 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="vorbis_encoder">
<title><varname>vorbis</varname></title>
<para>
- Encodes into Ogg Vorbis.
+ Encodes into <ulink url="http://www.vorbis.com/">Ogg
+ Vorbis</ulink>.
</para>
<informaltable>
@@ -1262,14 +2258,17 @@ systemctl start mpd.socket</programlisting>
</section>
</section>
- <section>
+ <section id="output_plugins">
<title>Output plugins</title>
- <section>
+ <section id="alsa_output">
<title><varname>alsa</varname></title>
<para>
- The "Advanced Linux Sound Architecture" plugin uses
+ The <ulink
+ url="http://www.alsa-project.org/"><application>Advanced
+ Linux Sound Architecture</application>
+ (<application>ALSA</application>)</ulink> plugin uses
<filename>libasound</filename>. It is recommended if you
are using Linux.
</para>
@@ -1340,10 +2339,11 @@ systemctl start mpd.socket</programlisting>
<entry>
If set to <parameter>no</parameter>, then
<filename>libasound</filename> will not attempt to
- resample, handing the responsibility over to MPD.
- It is recommended to let MPD resample (with
- libsamplerate), because ALSA is quite poor at doing
- so.
+ resample, handing the responsibility over to
+ <application>MPD</application>. It is recommended
+ to let <application>MPD</application> resample (with
+ <application>libsamplerate</application>), because
+ ALSA is quite poor at doing so.
</entry>
</row>
<row>
@@ -1371,15 +2371,15 @@ systemctl start mpd.socket</programlisting>
</row>
<row>
<entry>
- <varname>dsd_usb</varname>
+ <varname>dop</varname>
<parameter>yes|no</parameter>
</entry>
<entry>
If set to <parameter>yes</parameter>, then DSD over
- USB according to the <ulink
- url="http://www.sonore.us/DoP_openStandard_1v1.pdf">pro
- posed standard by dCS and others</ulink> is enabled. This wraps
- DSD samples in fake 24 bit PCM, and is understood by
+ PCM according to the <ulink
+ url="http://dsd-guide.com/dop-open-standard">DoP
+ standard</ulink> is enabled. This wraps DSD
+ samples in fake 24 bit PCM, and is understood by
some DSD capable products, but may be harmful to
other hardware. Therefore, the default is
<parameter>no</parameter> and you can enable the
@@ -1389,14 +2389,73 @@ systemctl start mpd.socket</programlisting>
</tbody>
</tgroup>
</informaltable>
+
+ <para>
+ The according hardware mixer plugin understands the
+ following settings:
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>mixer_device</varname>
+ <parameter>DEVICE</parameter>
+ </entry>
+ <entry>
+ <para>
+ Sets the ALSA mixer device name, defaulting to
+ <parameter>default</parameter> which lets ALSA
+ pick a value.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>mixer_control</varname>
+ <parameter>NAME</parameter>
+ </entry>
+ <entry>
+ <para>
+ Choose a mixer control, defaulting to
+ <parameter>PCM</parameter>. Type <command>amixer
+ scontrols</command> to get a list of available
+ mixer controls.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>mixer_index</varname>
+ <parameter>NUMBER</parameter>
+ </entry>
+ <entry>
+ Choose a mixer control index. This is necessary if
+ there is more than one control with the same name.
+ Defaults to <parameter>0</parameter> (the first
+ one).
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
</section>
<section>
<title><varname>ao</varname></title>
<para>
- The <varname>ao</varname> plugin uses the portable
- <filename>libao</filename> library.
+ The <varname>ao</varname> plugin uses the portable <ulink
+ url="https://www.xiph.org/ao/"><filename>libao</filename></ulink>
+ library. Use only if there is no native plugin for your
+ operating system.
</para>
<informaltable>
@@ -1479,15 +2538,19 @@ systemctl start mpd.socket</programlisting>
<entry>
This specifies the path of the FIFO to write to.
Must be an absolute path. If the path does not
- exist, it will be created when MPD is started, and
- removed when MPD is stopped. The FIFO will be
- created with the same user and group as MPD is
+ exist, it will be created when
+ <application>MPD</application> is started, and
+ removed when <application>MPD</application> is
+ stopped. The FIFO will be created with the same
+ user and group as <application>MPD</application> is
running as. Default permissions can be modified by
- using the builtin shell command "umask". If a FIFO
- already exists at the specified path it will be
- reused, and will not be removed when MPD is stopped.
- You can use the "mkfifo" command to create this, and
- then you may modify the permissions to your liking.
+ using the builtin shell command
+ <filename>umask</filename>. If a FIFO already
+ exists at the specified path it will be reused, and
+ will not be removed when
+ <application>MPD</application> is stopped. You can
+ use the "mkfifo" command to create this, and then
+ you may modify the permissions to your liking.
</entry>
</row>
</tbody>
@@ -1499,7 +2562,8 @@ systemctl start mpd.socket</programlisting>
<title><varname>jack</varname></title>
<para>
- The <varname>jack</varname> plugin connects to a JACK
+ The <varname>jack</varname> plugin connects to a <ulink
+ url="http://jackaudio.org/"><application>JACK</application></ulink>
server.
</para>
@@ -1518,8 +2582,8 @@ systemctl start mpd.socket</programlisting>
<parameter>NAME</parameter>
</entry>
<entry>
- The name of the JACK client. Defaults to "Music
- Player Daemon".
+ The name of the <application>JACK</application>
+ client. Defaults to "Music Player Daemon".
</entry>
</row>
<row>
@@ -1528,7 +2592,8 @@ systemctl start mpd.socket</programlisting>
<parameter>NAME</parameter>
</entry>
<entry>
- Optional name of the JACK server.
+ Optional name of the <application>JACK</application>
+ server.
</entry>
</row>
<row>
@@ -1539,7 +2604,8 @@ systemctl start mpd.socket</programlisting>
<entry>
If set to <parameter>yes</parameter>, then
<filename>libjack</filename> will automatically
- launch the JACK daemon. Disabled by default.
+ launch the <application>JACK</application> daemon.
+ Disabled by default.
</entry>
</row>
<row>
@@ -1548,10 +2614,10 @@ systemctl start mpd.socket</programlisting>
<parameter>A,B</parameter>
</entry>
<entry>
- The names of the JACK source ports to be created.
- By default, the ports "left" and "right" are
- created. To use more ports, you have to tweak this
- option.
+ The names of the <application>JACK</application>
+ source ports to be created. By default, the ports
+ "left" and "right" are created. To use more ports,
+ you have to tweak this option.
</entry>
</row>
<row>
@@ -1560,7 +2626,8 @@ systemctl start mpd.socket</programlisting>
<parameter>A,B</parameter>
</entry>
<entry>
- The names of the JACK destination ports to connect to.
+ The names of the <application>JACK</application>
+ destination ports to connect to.
</entry>
</row>
<row>
@@ -1579,21 +2646,23 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="httpd_output">
<title><varname>httpd</varname></title>
<para>
The <varname>httpd</varname> plugin creates a HTTP server,
- similar to ShoutCast / IceCast. HTTP streaming clients like
- <filename>mplayer</filename> can connect to it.
+ similar to <ulink
+ url="http://www.shoutcast.com/"><application>ShoutCast</application></ulink>
+ / <ulink
+ url="http://icecast.org/"><application>IceCast</application></ulink>.
+ HTTP streaming clients like
+ <application>mplayer</application> can connect to it.
</para>
<para>
- You must configure either <varname>quality</varname> or
- <varname>bitrate</varname>. It is highly recommended to
- configure a fixed <varname>format</varname>, because a
- stream cannot switch its audio format on-the-fly when the
- song changes.
+ It is highly recommended to configure a fixed
+ <varname>format</varname>, because a stream cannot switch
+ its audio format on-the-fly when the song changes.
</para>
<informaltable>
@@ -1630,28 +2699,10 @@ systemctl start mpd.socket</programlisting>
<parameter>NAME</parameter>
</entry>
<entry>
- Chooses an encoder plugin,
- e.g. <parameter>vorbis</parameter>.
- </entry>
- </row>
- <row>
- <entry>
- <varname>quality</varname>
- <parameter>Q</parameter>
- </entry>
- <entry>
- Configures the encoder quality (for VBR) in the
- range -1 .. 10.
- </entry>
- </row>
- <row>
- <entry>
- <varname>bitrate</varname>
- <parameter>BR</parameter>
- </entry>
- <entry>
- Sets a constant encoder bit rate, in kilobit per
- second.
+ Chooses an encoder plugin. A list of encoder
+ plugins can be found in the <link
+ linkend="encoder_plugins">encoder plugin
+ reference</link>.
</entry>
</row>
<row>
@@ -1703,7 +2754,7 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="oss_output">
<title><varname>oss</varname></title>
<para>
@@ -1711,6 +2762,13 @@ systemctl start mpd.socket</programlisting>
platforms.
</para>
+ <para>
+ On Linux, <application>OSS</application> has been superseded
+ by <application>ALSA</application>. Use the <link
+ linkend="alsa_output"><application>ALSA</application> output
+ plugin</link> instead of this one on Linux.
+ </para>
+
<informaltable>
<tgroup cols="2">
<thead>
@@ -1727,22 +2785,66 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Sets the path of the PCM device. If not specified,
- then MPD will attempt to open
- <filename>/dev/sound/dsp</filename> and
+ then <application>MPD</application> will attempt to
+ open <filename>/dev/sound/dsp</filename> and
<filename>/dev/dsp</filename>.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
+
+ <para>
+ The according hardware mixer plugin understands the
+ following settings:
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>mixer_device</varname>
+ <parameter>DEVICE</parameter>
+ </entry>
+ <entry>
+ <para>
+ Sets the OSS mixer device path, defaulting to
+ <filename>/dev/mixer</filename>.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>mixer_control</varname>
+ <parameter>NAME</parameter>
+ </entry>
+ <entry>
+ <para>
+ Choose a mixer control, defaulting to
+ <parameter>PCM</parameter>.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
</section>
<section>
<title><varname>openal</varname></title>
<para>
- The "OpenAL" plugin uses <filename>libopenal</filename>.
- It is supported on many platforms.
+ The "OpenAL" plugin uses <ulink
+ url="http://kcat.strangesoft.net/openal.html"><filename>libopenal</filename></ulink>.
+ It is supported on many platforms. Use only if there is no
+ native plugin for your operating system.
</para>
<informaltable>
@@ -1809,11 +2911,12 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="pulse_output">
<title><varname>pulse</varname></title>
<para>
- The <varname>pulse</varname> plugin connects to a PulseAudio
+ The <varname>pulse</varname> plugin connects to a <ulink
+ url="http://www.freedesktop.org/wiki/Software/PulseAudio/"><application>PulseAudio</application></ulink>
server.
</para>
@@ -1832,8 +2935,10 @@ systemctl start mpd.socket</programlisting>
<parameter>HOSTNAME</parameter>
</entry>
<entry>
- Sets the host name of the PulseAudio server. By
- default, MPD connects to the local PulseAudio
+ Sets the host name of the
+ <application>PulseAudio</application> server. By
+ default, <application>MPD</application> connects to
+ the local <application>PulseAudio</application>
server.
</entry>
</row>
@@ -1843,8 +2948,9 @@ systemctl start mpd.socket</programlisting>
<parameter>NAME</parameter>
</entry>
<entry>
- Specifies the name of the PulseAudio sink MPD should
- play on.
+ Specifies the name of the
+ <application>PulseAudio</application> sink
+ <application>MPD</application> should play on.
</entry>
</row>
</tbody>
@@ -1877,8 +2983,8 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
The host name of the RoarAudio server. If not
- specified, then MPD will connect to the default
- locations.
+ specified, then <application>MPD</application> will
+ connect to the default locations.
</entry>
</row>
@@ -1888,8 +2994,9 @@ systemctl start mpd.socket</programlisting>
<parameter>ROLE</parameter>
</entry>
<entry>
- The "role" that MPD registers itself as in the
- RoarAudio server. The default is "music".
+ The "role" that <application>MPD</application>
+ registers itself as in the RoarAudio server. The
+ default is "music".
</entry>
</row>
</tbody>
@@ -1902,13 +3009,8 @@ systemctl start mpd.socket</programlisting>
<para>
The <varname>recorder</varname> plugin writes the audio
- played by MPD to a file. This may be useful for recording
- radio streams.
- </para>
-
- <para>
- You must configure either <varname>quality</varname> or
- <varname>bitrate</varname>.
+ played by <application>MPD</application> to a file. This
+ may be useful for recording radio streams.
</para>
<informaltable>
@@ -1935,28 +3037,10 @@ systemctl start mpd.socket</programlisting>
<parameter>NAME</parameter>
</entry>
<entry>
- Chooses an encoder plugin,
- e.g. <parameter>vorbis</parameter>.
- </entry>
- </row>
- <row>
- <entry>
- <varname>quality</varname>
- <parameter>Q</parameter>
- </entry>
- <entry>
- Configures the encoder quality (for VBR) in the
- range -1 .. 10.
- </entry>
- </row>
- <row>
- <entry>
- <varname>bitrate</varname>
- <parameter>BR</parameter>
- </entry>
- <entry>
- Sets a constant encoder bit rate, in kilobit per
- second.
+ Chooses an encoder plugin. A list of encoder
+ plugins can be found in the <link
+ linkend="encoder_plugins">encoder plugin
+ reference</link>.
</entry>
</row>
</tbody>
@@ -1964,12 +3048,15 @@ systemctl start mpd.socket</programlisting>
</informaltable>
</section>
- <section>
+ <section id="shout_output">
<title><varname>shout</varname></title>
<para>
- The <varname>shout</varname> plugin connects to a ShoutCast
- or IceCast server. It forwards tags to this server.
+ The <varname>shout</varname> plugin connects to a <ulink
+ url="http://www.shoutcast.com/"><application>ShoutCast</application></ulink>
+ or <ulink
+ url="http://icecast.org/"><application>IceCast</application></ulink>
+ server. It forwards tags to this server.
</para>
<para>
@@ -1991,7 +3078,11 @@ systemctl start mpd.socket</programlisting>
<parameter>HOSTNAME</parameter>
</entry>
<entry>
- Sets the host name of the Shoutcast/Icecast server.
+ Sets the host name of the <ulink
+ url="http://www.shoutcast.com/"><application>ShoutCast</application></ulink>
+ / <ulink
+ url="http://icecast.org/"><application>IceCast</application></ulink>
+ server.
</entry>
</row>
<row>
@@ -2020,8 +3111,8 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Specifies the protocol that wil be used to connect
- to the icecast/shoutcast server. The default
- is "<parameter>icecast2</parameter>".
+ to the server. The default is
+ "<parameter>icecast2</parameter>".
</entry>
</row>
@@ -2031,7 +3122,8 @@ systemctl start mpd.socket</programlisting>
<parameter>URI</parameter>
</entry>
<entry>
- Mounts the MPD stream in the specified URI.
+ Mounts the <application>MPD</application> stream in
+ the specified URI.
</entry>
</row>
<row>
@@ -2097,7 +3189,7 @@ systemctl start mpd.socket</programlisting>
</entry>
<entry>
Specifies whether the stream should be "public".
- Default is "no".
+ Default is <parameter>no</parameter>.
</entry>
</row>
<row>
@@ -2106,10 +3198,11 @@ systemctl start mpd.socket</programlisting>
<parameter>PLUGIN</parameter>
</entry>
<entry>
- Sets the name of the encoder plugin. Default is
- "vorbis". "vorbis" and "lame" are valid encoder
- plugins (provided that you enabled them at compile
- time).
+ Chooses an encoder plugin. Default is <link
+ linkend="vorbis_encoder"><parameter>vorbis</parameter></link>.
+ A list of encoder plugins can be found in the <link
+ linkend="encoder_plugins">encoder plugin
+ reference</link>.
</entry>
</row>
</tbody>
@@ -2150,7 +3243,7 @@ systemctl start mpd.socket</programlisting>
</section>
</section>
- <section>
+ <section id="playlist_plugins">
<title>Playlist plugins</title>
<section>
@@ -2214,6 +3307,49 @@ systemctl start mpd.socket</programlisting>
</para>
</section>
+ <section>
+ <title><varname>soundcloud</varname></title>
+
+ <para>
+ Adds <ulink url="https://www.soundcloud.com/">Soundcloud</ulink>
+ playlists. SoundCloud playlists use the <filename>soundcloud://</filename> URI,
+ and with a number of arguments, you may load different playlists with
+ </para>
+
+ <programlisting>
+mpc load soundcloud://track/TRACK_ID
+mpc load soundcloud://playlist/PLAYLIST_ID
+mpc load soundcloud://user/USERNAME
+mpc load soundcloud://search/SEARCH_QUERY
+mpc load soundcloud://url/https://soundcloud.com/ARTIST/TRACK-NAME
+ </programlisting>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>apikey</varname>
+ <parameter>client_id</parameter>
+ </entry>
+ <entry>
+ User apikey/client_id can override the
+ <application>MPD</application> token provided by
+ SoundCloud.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+
+ </section>
+
</section>
</chapter>
</book>
diff --git a/m4/ax_append_compile_flags.m4 b/m4/ax_append_compile_flags.m4
index 425417008..dc7b86600 100644
--- a/m4/ax_append_compile_flags.m4
+++ b/m4/ax_append_compile_flags.m4
@@ -54,10 +54,12 @@
# 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
+#serial 4
AC_DEFUN([AX_APPEND_COMPILE_FLAGS],
-[for flag in $1; do
+[AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG])
+AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
+for flag in $1; do
AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3])
done
])dnl AX_APPEND_COMPILE_FLAGS
diff --git a/m4/ax_append_link_flags.m4 b/m4/ax_append_link_flags.m4
index 4fc433700..c73ddafc2 100644
--- a/m4/ax_append_link_flags.m4
+++ b/m4/ax_append_link_flags.m4
@@ -52,10 +52,12 @@
# 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
+#serial 4
AC_DEFUN([AX_APPEND_LINK_FLAGS],
-[for flag in $1; do
+[AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
+AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
+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_boost_base.m4 b/m4/ax_boost_base.m4
new file mode 100644
index 000000000..8e6ee9a9b
--- /dev/null
+++ b/m4/ax_boost_base.m4
@@ -0,0 +1,272 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_boost_base.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# DESCRIPTION
+#
+# Test for the Boost C++ libraries of a particular version (or newer)
+#
+# If no path to the installed boost library is given the macro searchs
+# under /usr, /usr/local, /opt and /opt/local and evaluates the
+# $BOOST_ROOT environment variable. Further documentation is available at
+# <http://randspringer.de/boost/index.html>.
+#
+# This macro calls:
+#
+# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS)
+#
+# And sets:
+#
+# HAVE_BOOST
+#
+# LICENSE
+#
+# Copyright (c) 2008 Thomas Porschberg <thomas@randspringer.de>
+# Copyright (c) 2009 Peter Adolphs
+#
+# 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 23
+
+AC_DEFUN([AX_BOOST_BASE],
+[
+AC_ARG_WITH([boost],
+ [AS_HELP_STRING([--with-boost@<:@=ARG@:>@],
+ [use Boost library from a standard location (ARG=yes),
+ from the specified location (ARG=<path>),
+ or disable it (ARG=no)
+ @<:@ARG=yes@:>@ ])],
+ [
+ if test "$withval" = "no"; then
+ want_boost="no"
+ elif test "$withval" = "yes"; then
+ want_boost="yes"
+ ac_boost_path=""
+ else
+ want_boost="yes"
+ ac_boost_path="$withval"
+ fi
+ ],
+ [want_boost="yes"])
+
+
+AC_ARG_WITH([boost-libdir],
+ AS_HELP_STRING([--with-boost-libdir=LIB_DIR],
+ [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]),
+ [
+ if test -d "$withval"
+ then
+ ac_boost_lib_path="$withval"
+ else
+ AC_MSG_ERROR(--with-boost-libdir expected directory name)
+ fi
+ ],
+ [ac_boost_lib_path=""]
+)
+
+if test "x$want_boost" = "xyes"; then
+ boost_lib_version_req=ifelse([$1], ,1.20.0,$1)
+ boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'`
+ boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'`
+ boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'`
+ boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
+ if test "x$boost_lib_version_req_sub_minor" = "x" ; then
+ boost_lib_version_req_sub_minor="0"
+ fi
+ WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor`
+ AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req)
+ succeeded=no
+
+ dnl On 64-bit systems check for system libraries in both lib64 and lib.
+ dnl The former is specified by FHS, but e.g. Debian does not adhere to
+ dnl this (as it rises problems for generic multi-arch support).
+ dnl The last entry in the list is chosen by default when no libraries
+ dnl are found, e.g. when only header-only libraries are installed!
+ libsubdirs="lib"
+ ax_arch=`uname -m`
+ case $ax_arch in
+ x86_64|ppc64|s390x|sparc64|aarch64)
+ libsubdirs="lib64 lib lib64"
+ ;;
+ esac
+
+ dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give
+ dnl them priority over the other paths since, if libs are found there, they
+ dnl are almost assuredly the ones desired.
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs"
+
+ case ${host_cpu} in
+ i?86)
+ libsubdirs="lib/i386-${host_os} $libsubdirs"
+ ;;
+ esac
+
+ dnl first we check the system location for boost libraries
+ dnl this location ist chosen if boost libraries are installed with the --layout=system option
+ dnl or if you install boost with RPM
+ if test "$ac_boost_path" != ""; then
+ BOOST_CPPFLAGS="-I$ac_boost_path/include"
+ for ac_boost_path_tmp in $libsubdirs; do
+ if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then
+ BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp"
+ break
+ fi
+ done
+ elif test "$cross_compiling" != yes; then
+ for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do
+ if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then
+ for libsubdir in $libsubdirs ; do
+ if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
+ done
+ BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir"
+ BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include"
+ break;
+ fi
+ done
+ fi
+
+ dnl overwrite ld flags if we have required special directory with
+ dnl --with-boost-libdir parameter
+ if test "$ac_boost_lib_path" != ""; then
+ BOOST_LDFLAGS="-L$ac_boost_lib_path"
+ fi
+
+ CPPFLAGS_SAVED="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+ export CPPFLAGS
+
+ LDFLAGS_SAVED="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+ export LDFLAGS
+
+ AC_REQUIRE([AC_PROG_CXX])
+ AC_LANG_PUSH(C++)
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+ @%:@include <boost/version.hpp>
+ ]], [[
+ #if BOOST_VERSION >= $WANT_BOOST_VERSION
+ // Everything is okay
+ #else
+ # error Boost version is too old
+ #endif
+ ]])],[
+ AC_MSG_RESULT(yes)
+ succeeded=yes
+ found_system=yes
+ ],[
+ ])
+ AC_LANG_POP([C++])
+
+
+
+ dnl if we found no boost with system layout we search for boost libraries
+ dnl built and installed without the --layout=system option or for a staged(not installed) version
+ if test "x$succeeded" != "xyes"; then
+ _version=0
+ if test "$ac_boost_path" != ""; then
+ if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then
+ for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do
+ _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
+ V_CHECK=`expr $_version_tmp \> $_version`
+ if test "$V_CHECK" = "1" ; then
+ _version=$_version_tmp
+ fi
+ VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
+ BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE"
+ done
+ fi
+ else
+ if test "$cross_compiling" != yes; then
+ for ac_boost_path in /usr /usr/local /opt /opt/local ; do
+ if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then
+ for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do
+ _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
+ V_CHECK=`expr $_version_tmp \> $_version`
+ if test "$V_CHECK" = "1" ; then
+ _version=$_version_tmp
+ best_path=$ac_boost_path
+ fi
+ done
+ fi
+ done
+
+ VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
+ BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE"
+ if test "$ac_boost_lib_path" = ""; then
+ for libsubdir in $libsubdirs ; do
+ if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
+ done
+ BOOST_LDFLAGS="-L$best_path/$libsubdir"
+ fi
+ fi
+
+ if test "x$BOOST_ROOT" != "x"; then
+ for libsubdir in $libsubdirs ; do
+ if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
+ done
+ if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then
+ version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'`
+ stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'`
+ stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'`
+ V_CHECK=`expr $stage_version_shorten \>\= $_version`
+ if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then
+ AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT)
+ BOOST_CPPFLAGS="-I$BOOST_ROOT"
+ BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir"
+ fi
+ fi
+ fi
+ fi
+
+ CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+ export CPPFLAGS
+ LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+ export LDFLAGS
+
+ AC_LANG_PUSH(C++)
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+ @%:@include <boost/version.hpp>
+ ]], [[
+ #if BOOST_VERSION >= $WANT_BOOST_VERSION
+ // Everything is okay
+ #else
+ # error Boost version is too old
+ #endif
+ ]])],[
+ AC_MSG_RESULT(yes)
+ succeeded=yes
+ found_system=yes
+ ],[
+ ])
+ AC_LANG_POP([C++])
+ fi
+
+ if test "$succeeded" != "yes" ; then
+ if test "$_version" = "0" ; then
+ AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in <boost/version.hpp>. See http://randspringer.de/boost for more documentation.]])
+ else
+ AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).])
+ fi
+ # execute ACTION-IF-NOT-FOUND (if present):
+ ifelse([$3], , :, [$3])
+ else
+ AC_SUBST(BOOST_CPPFLAGS)
+ AC_SUBST(BOOST_LDFLAGS)
+ AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available])
+ # execute ACTION-IF-FOUND (if present):
+ ifelse([$2], , :, [$2])
+ fi
+
+ CPPFLAGS="$CPPFLAGS_SAVED"
+ LDFLAGS="$LDFLAGS_SAVED"
+fi
+
+])
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
index c3a8d695a..51df0c09a 100644
--- a/m4/ax_check_compile_flag.m4
+++ b/m4/ax_check_compile_flag.m4
@@ -4,7 +4,7 @@
#
# SYNOPSIS
#
-# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])
+# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
@@ -19,6 +19,8 @@
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
# force the compiler to issue an error when a bad flag is given.
#
+# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
#
@@ -53,7 +55,7 @@
# 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
+#serial 3
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
@@ -61,7 +63,7 @@ AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
_AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
- AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
+ AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
diff --git a/m4/ax_check_link_flag.m4 b/m4/ax_check_link_flag.m4
index e2d0d363e..db899ddd0 100644
--- a/m4/ax_check_link_flag.m4
+++ b/m4/ax_check_link_flag.m4
@@ -4,7 +4,7 @@
#
# SYNOPSIS
#
-# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])
+# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
@@ -19,6 +19,8 @@
# EXTRA-FLAGS FLAG". This can for example be used to force the linker to
# issue an error when a bad flag is given.
#
+# INPUT gives an alternative input source to AC_LINK_IFELSE.
+#
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG.
#
@@ -53,14 +55,14 @@
# 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
+#serial 3
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()],
+ AC_LINK_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
LDFLAGS=$ax_check_save_flags])
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 000000000..d383ad5c6
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,332 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC to any special C compiler that is needed for
+# multi-threaded programs (defaults to the value of CC otherwise). (This
+# is necessary on AIX to use the special cc_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also link it with them as well. e.g. you should link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threads programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name
+# (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+#
+# This program is free software: you can 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 21
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on True64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
+ AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test x"$ax_pthread_ok" = xno; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
+# -pthreads: Solaris/gcc
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads too;
+# also defines -D_REENTRANT)
+# ... -mt is also the pthreads flag for HP/aCC
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case ${host_os} in
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (We need to link with -pthreads/-mt/
+ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather
+ # a function called by this macro, so we could check for that, but
+ # who knows whether they'll stub that too in a future libc.) So,
+ # we'll just look for -pthreads and -lpthread first:
+
+ ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags"
+ ;;
+
+ darwin*)
+ ax_pthread_flags="-pthread $ax_pthread_flags"
+ ;;
+esac
+
+# Clang doesn't consider unrecognized options an error unless we specify
+# -Werror. We throw in some extra Clang-specific options to ensure that
+# this doesn't happen for GCC, which also accepts -Werror.
+
+AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags])
+save_CFLAGS="$CFLAGS"
+ax_pthread_extra_flags="-Werror"
+CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument"
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])],
+ [AC_MSG_RESULT([yes])],
+ [ax_pthread_extra_flags=
+ AC_MSG_RESULT([no])])
+CFLAGS="$save_CFLAGS"
+
+if test x"$ax_pthread_ok" = xno; then
+for flag in $ax_pthread_flags; do
+
+ case $flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $flag])
+ PTHREAD_CFLAGS="$flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ if test x"$ax_pthread_config" = xno; then continue; fi
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$flag])
+ PTHREAD_LIBS="-l$flag"
+ ;;
+ esac
+
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+ static void routine(void *a) { a = 0; }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = xyes; then
+ break;
+ fi
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$ax_pthread_ok" = xyes; then
+ save_LIBS="$LIBS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_MSG_CHECKING([for joinable pthread attribute])
+ attr_name=unknown
+ for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $attr; return attr /* ; */])],
+ [attr_name=$attr; break],
+ [])
+ done
+ AC_MSG_RESULT([$attr_name])
+ if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
+ AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ fi
+
+ AC_MSG_CHECKING([if more special flags are required for pthreads])
+ flag=no
+ case ${host_os} in
+ aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";;
+ osf* | hpux*) flag="-D_REENTRANT";;
+ solaris*)
+ if test "$GCC" = "yes"; then
+ flag="-D_REENTRANT"
+ else
+ # TODO: What about Clang on Solaris?
+ flag="-mt -D_REENTRANT"
+ fi
+ ;;
+ esac
+ AC_MSG_RESULT([$flag])
+ if test "x$flag" != xno; then
+ PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
+ fi
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT], [
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])])
+
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != xyes; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
+ [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test x"$ax_pthread_ok" = xyes; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/m4/ax_require_defined.m4 b/m4/ax_require_defined.m4
new file mode 100644
index 000000000..cae11112d
--- /dev/null
+++ b/m4/ax_require_defined.m4
@@ -0,0 +1,37 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_require_defined.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_REQUIRE_DEFINED(MACRO)
+#
+# DESCRIPTION
+#
+# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
+# been defined and thus are available for use. This avoids random issues
+# where a macro isn't expanded. Instead the configure script emits a
+# non-fatal:
+#
+# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
+#
+# It's like AC_REQUIRE except it doesn't expand the required macro.
+#
+# Here's an example:
+#
+# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
+#
+# LICENSE
+#
+# Copyright (c) 2014 Mike Frysinger <vapier@gentoo.org>
+#
+# 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 1
+
+AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
+ m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
+])dnl AX_REQUIRE_DEFINED
diff --git a/m4/mpd_depends.m4 b/m4/mpd_depends.m4
new file mode 100644
index 000000000..4898f9084
--- /dev/null
+++ b/m4/mpd_depends.m4
@@ -0,0 +1,9 @@
+AC_DEFUN([MPD_DEPENDS], [
+ if test x$$2 = xno; then
+ if test x$$1 = xauto; then
+ $1=no
+ elif test x$$1 = xyes; then
+ AC_MSG_ERROR([$3])
+ fi
+ fi
+])
diff --git a/m4/mpd_func.m4 b/m4/mpd_func.m4
index d12d27062..5f2bf8f3d 100644
--- a/m4/mpd_func.m4
+++ b/m4/mpd_func.m4
@@ -10,3 +10,16 @@ AC_DEFUN([MPD_OPTIONAL_FUNC], [
[AC_CHECK_FUNC([$2],
[AC_DEFINE([$3], 1, [Define to use $1])],)])
])
+
+dnl MPD_OPTIONAL_FUNC_NODEF(name, func)
+dnl
+dnl Allow the user to enable or disable the use of a function.
+dnl Works similar to MPD_OPTIONAL_FUNC, however MPD_OPTIONAL_FUNC_NODEF
+dnl does not invoke AC_DEFINE when function is enabled. Shell variable
+dnl enable_$name is set to "yes" instead.
+AC_DEFUN([MPD_OPTIONAL_FUNC_NODEF], [
+ AC_ARG_ENABLE([$1],
+ AS_HELP_STRING([--enable-$1],
+ [use the function "$1" (default: auto)]),,
+ [AC_CHECK_FUNC([$2], [enable_$1=yes],)])
+])
diff --git a/m4/pkg.m4 b/m4/pkg.m4
index c29b6c057..c5b26b52e 100644
--- a/m4/pkg.m4
+++ b/m4/pkg.m4
@@ -1,4 +1,5 @@
# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
+# serial 1 (pkg-config-0.24)
#
# Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
#
@@ -25,8 +26,12 @@
# ----------------------------------
AC_DEFUN([PKG_PROG_PKG_CONFIG],
[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
-m4_pattern_allow([^PKG_CONFIG(_PATH)?$])
-AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl
+m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
+m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
+AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
+AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
+
if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
fi
@@ -39,7 +44,6 @@ if test -n "$PKG_CONFIG"; then
AC_MSG_RESULT([no])
PKG_CONFIG=""
fi
-
fi[]dnl
])# PKG_PROG_PKG_CONFIG
@@ -48,34 +52,32 @@ fi[]dnl
# Check to see whether a particular set of modules exists. Similar
# to PKG_CHECK_MODULES(), but does not set variables or print errors.
#
-#
-# Similar to PKG_CHECK_MODULES, make sure that the first instance of
-# this or PKG_CHECK_MODULES is called, or make sure to call
-# PKG_CHECK_EXISTS manually
+# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+# only at the first occurence in configure.ac, so if the first place
+# it's called might be skipped (such as if it is within an "if", you
+# have to call PKG_CHECK_EXISTS manually
# --------------------------------------------------------------
AC_DEFUN([PKG_CHECK_EXISTS],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
if test -n "$PKG_CONFIG" && \
AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
- m4_ifval([$2], [$2], [:])
+ m4_default([$2], [:])
m4_ifvaln([$3], [else
$3])dnl
fi])
-
# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
# ---------------------------------------------
m4_define([_PKG_CONFIG],
-[if test -n "$PKG_CONFIG"; then
- if test -n "$$1"; then
- pkg_cv_[]$1="$$1"
- else
- PKG_CHECK_EXISTS([$3],
- [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`],
- [pkg_failed=yes])
- fi
-else
- pkg_failed=untried
+[if test -n "$$1"; then
+ pkg_cv_[]$1="$$1"
+ elif test -n "$PKG_CONFIG"; then
+ PKG_CHECK_EXISTS([$3],
+ [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes ],
+ [pkg_failed=yes])
+ else
+ pkg_failed=untried
fi[]dnl
])# _PKG_CONFIG
@@ -117,16 +119,17 @@ and $1[]_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.])
if test $pkg_failed = yes; then
+ AC_MSG_RESULT([no])
_PKG_SHORT_ERRORS_SUPPORTED
if test $_pkg_short_errors_supported = yes; then
- $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"`
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1`
else
- $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"`
+ $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
- ifelse([$4], , [AC_MSG_ERROR(dnl
+ m4_default([$4], [AC_MSG_ERROR(
[Package requirements ($2) were not met:
$$1_PKG_ERRORS
@@ -134,24 +137,78 @@ $$1_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
-_PKG_TEXT
-])],
- [AC_MSG_RESULT([no])
- $4])
+_PKG_TEXT])[]dnl
+ ])
elif test $pkg_failed = untried; then
- ifelse([$4], , [AC_MSG_FAILURE(dnl
+ AC_MSG_RESULT([no])
+ m4_default([$4], [AC_MSG_FAILURE(
[The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
_PKG_TEXT
-To get pkg-config, see <http://www.freedesktop.org/software/pkgconfig>.])],
- [$4])
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl
+ ])
else
$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
AC_MSG_RESULT([yes])
- ifelse([$3], , :, [$3])
+ $3
fi[]dnl
])# PKG_CHECK_MODULES
+
+
+# PKG_INSTALLDIR(DIRECTORY)
+# -------------------------
+# Substitutes the variable pkgconfigdir as the location where a module
+# should install pkg-config .pc files. By default the directory is
+# $libdir/pkgconfig, but the default can be changed by passing
+# DIRECTORY. The user can override through the --with-pkgconfigdir
+# parameter.
+AC_DEFUN([PKG_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+ [pkg-config installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([pkgconfigdir],
+ [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,
+ [with_pkgconfigdir=]pkg_default)
+AC_SUBST([pkgconfigdir], [$with_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+]) dnl PKG_INSTALLDIR
+
+
+# PKG_NOARCH_INSTALLDIR(DIRECTORY)
+# -------------------------
+# Substitutes the variable noarch_pkgconfigdir as the location where a
+# module should install arch-independent pkg-config .pc files. By
+# default the directory is $datadir/pkgconfig, but the default can be
+# changed by passing DIRECTORY. The user can override through the
+# --with-noarch-pkgconfigdir parameter.
+AC_DEFUN([PKG_NOARCH_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+ [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([noarch-pkgconfigdir],
+ [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,
+ [with_noarch_pkgconfigdir=]pkg_default)
+AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+]) dnl PKG_NOARCH_INSTALLDIR
+
+
+# PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
+# [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+# -------------------------------------------
+# Retrieves the value of the pkg-config variable for the given module.
+AC_DEFUN([PKG_CHECK_VAR],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
+
+_PKG_CONFIG([$1], [variable="][$3]["], [$2])
+AS_VAR_COPY([$1], [pkg_cv_][$1])
+
+AS_VAR_IF([$1], [""], [$5], [$4])dnl
+])# PKG_CHECK_VAR
diff --git a/m4/ucred.m4 b/m4/ucred.m4
index cdc6ea3b3..f53649985 100644
--- a/m4/ucred.m4
+++ b/m4/ucred.m4
@@ -1,5 +1,4 @@
-# Check if "struct ucred" is available. If not, try harder with
-# _GNU_SOURCE.
+# Check if "struct ucred" is available.
#
# Author: Max Kellermann <max@duempel.org>
@@ -10,19 +9,6 @@ AC_DEFUN([STRUCT_UCRED],[
[struct ucred cred;],
mpd_cv_have_struct_ucred=yes,
mpd_cv_have_struct_ucred=no)
- if test x$mpd_cv_have_struct_ucred = xno; then
- # glibc 2.8 forces _GNU_SOURCE on us
- AC_TRY_COMPILE(
- [#define _GNU_SOURCE
- #include <sys/socket.h>],
- [struct ucred cred;],
- mpd_cv_have_struct_ucred=yes,
- mpd_cv_have_struct_ucred=no)
- if test x$mpd_cv_have_struct_ucred = xyes; then
- # :(
- CFLAGS="$CFLAGS -D_GNU_SOURCE"
- fi
- fi
])
AC_MSG_RESULT($mpd_cv_have_struct_ucred)
diff --git a/mpd.service.in b/mpd.service.in
deleted file mode 100644
index 65fffa7bb..000000000
--- a/mpd.service.in
+++ /dev/null
@@ -1,9 +0,0 @@
-[Unit]
-Description=Music Player Daemon
-After=network.target sound.target
-
-[Service]
-ExecStart=@prefix@/bin/mpd --no-daemon
-
-[Install]
-WantedBy=multi-user.target
diff --git a/mpd.svg b/mpd.svg
new file mode 100644
index 000000000..2988dfafc
--- /dev/null
+++ b/mpd.svg
@@ -0,0 +1,857 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ x="0"
+ y="0"
+ width="128"
+ height="128"
+ id="svg1"
+ sodipodi:version="0.32"
+ sodipodi:docname="mpd.svg"
+ inkscape:version="0.47pre4 r22446"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-test5.png"
+ inkscape:export-xdpi="76.799988"
+ inkscape:export-ydpi="76.799988">
+ <sodipodi:namedview
+ id="base"
+ inkscape:zoom="2.6884788"
+ inkscape:cx="71.610485"
+ inkscape:cy="61.484977"
+ inkscape:window-width="1680"
+ inkscape:window-height="994"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ showgrid="false"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 80 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="160 : 80 : 1"
+ inkscape:persp3d-origin="80 : 53.333333 : 1"
+ id="perspective118" />
+ <linearGradient
+ id="linearGradient919">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.86092716;"
+ offset="0.0000000"
+ id="stop920" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.0000000;"
+ offset="1.0000000"
+ id="stop921" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient1068">
+ <stop
+ offset="0.0000000"
+ style="stop-color:#d2d2d2;stop-opacity:1.0000000;"
+ id="stop1070" />
+ <stop
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ id="stop1069" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient1065">
+ <stop
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ id="stop1067" />
+ <stop
+ offset="1.0000000"
+ style="stop-color:#c2bfbf;stop-opacity:0.99607843;"
+ id="stop1066" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient1060">
+ <stop
+ offset="0.0000000"
+ style="stop-color:#878787;stop-opacity:1.0000000;"
+ id="stop1063" />
+ <stop
+ offset="1.0000000"
+ style="stop-color:#000000;stop-opacity:0.99607843;"
+ id="stop1061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient645">
+ <stop
+ style="stop-color:#aca597;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop646" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop647" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient593">
+ <stop
+ style="stop-color:#478acf;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop594" />
+ <stop
+ style="stop-color:#65c6f7;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop595" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient574">
+ <stop
+ style="stop-color:#85ad92;stop-opacity:1.0000;"
+ offset="0"
+ id="stop575" />
+ <stop
+ style="stop-color:#559db2;stop-opacity:0.7725;"
+ offset="1"
+ id="stop576" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient570">
+ <stop
+ style="stop-color:#999999;stop-opacity:0.7176;"
+ offset="0"
+ id="stop571" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.3725;"
+ offset="1"
+ id="stop572" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient573"
+ xlink:href="#linearGradient1068"
+ x1="40.458553"
+ y1="389.65582"
+ x2="36.063946"
+ y2="357.28375"
+ gradientTransform="matrix(2.3025192,0,0,0.29683004,-0.91913426,-1.5117091)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient1213"
+ xlink:href="#linearGradient1068"
+ x1="123.71407"
+ y1="141.41566"
+ x2="98.353867"
+ y2="113.41083"
+ gradientTransform="matrix(0.91680324,0,0,0.74547827,-0.91913426,-1.5117091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ id="radialGradient581"
+ xlink:href="#linearGradient919"
+ cx="0.095785439"
+ cy="0.16814159"
+ r="1.5409589"
+ fx="0.095785439"
+ fy="0.16814159" />
+ <linearGradient
+ id="linearGradient580"
+ xlink:href="#linearGradient1068"
+ x1="132.0352"
+ y1="135.68469"
+ x2="119.62381"
+ y2="111.07157"
+ gradientTransform="matrix(0.90170536,0,0,0.75796032,-0.91913426,-1.5117091)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ xlink:href="#linearGradient1060"
+ id="linearGradient901"
+ x1="0.93491787"
+ y1="0.92044502"
+ x2="-0.052546836"
+ y2="0.20347559" />
+ <linearGradient
+ xlink:href="#linearGradient593"
+ id="linearGradient902" />
+ <linearGradient
+ xlink:href="#linearGradient1068"
+ id="linearGradient916"
+ x1="0.14831461"
+ y1="-1.6875"
+ x2="0.43370786"
+ y2="1.8125" />
+ <defs
+ id="defs890">
+ <linearGradient
+ id="linearGradient922"
+ x1="0"
+ y1="0.5"
+ x2="1"
+ y2="0.5"
+ gradientUnits="objectBoundingBox"
+ spreadMethod="pad"
+ xlink:href="#linearGradient1065" />
+ <linearGradient
+ id="linearGradient908"
+ x1="0"
+ y1="0.5"
+ x2="1"
+ y2="0.5"
+ gradientUnits="objectBoundingBox"
+ spreadMethod="pad"
+ xlink:href="#linearGradient1060" />
+ <linearGradient
+ id="linearGradient894"
+ x1="0"
+ y1="0.5"
+ x2="1"
+ y2="0.5"
+ gradientUnits="objectBoundingBox"
+ spreadMethod="pad"
+ xlink:href="#linearGradient1068" />
+ <linearGradient
+ xlink:href="#linearGradient894"
+ id="linearGradient897"
+ x1="0.5955056"
+ y1="-0.33587787"
+ x2="0.61348313"
+ y2="1.1908396" />
+ <linearGradient
+ xlink:href="#linearGradient894"
+ id="linearGradient898"
+ x1="0.96449792"
+ y1="1.0278323"
+ x2="0.46738392"
+ y2="0.21800731" />
+ <linearGradient
+ xlink:href="#linearGradient908"
+ id="linearGradient907"
+ x1="0.57078654"
+ y1="2.3770492"
+ x2="0.33258426"
+ y2="0.49180329" />
+ <linearGradient
+ xlink:href="#linearGradient922"
+ id="linearGradient921"
+ x1="0.47058824"
+ y1="0.15384616"
+ x2="0.46547315"
+ y2="0.98380566" />
+ <linearGradient
+ xlink:href="#linearGradient922"
+ id="linearGradient948" />
+ <defs
+ id="defs987">
+ <linearGradient
+ id="linearGradient855"
+ x1="0"
+ y1="0.5"
+ x2="1"
+ y2="0.5"
+ gradientUnits="objectBoundingBox"
+ spreadMethod="pad"
+ xlink:href="#linearGradient908" />
+ <linearGradient
+ id="linearGradient1188"
+ x1="0"
+ y1="0.5"
+ x2="1"
+ y2="0.5"
+ gradientUnits="objectBoundingBox"
+ spreadMethod="pad"
+ xlink:href="#linearGradient922" />
+ <linearGradient
+ id="linearGradient831">
+ <stop
+ style="stop-color:#94897f;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop832" />
+ <stop
+ style="stop-color:#fff5fe;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop833" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient1188"
+ id="linearGradient834"
+ x1="0.87550199"
+ y1="0.34817815"
+ x2="-0.29317269"
+ y2="0.93522269"
+ gradientUnits="objectBoundingBox"
+ spreadMethod="pad" />
+ <radialGradient
+ xlink:href="#linearGradient1188"
+ id="radialGradient835"
+ r="0.55628061"
+ fy="0.28125"
+ fx="0.59090906"
+ cy="0.28125"
+ cx="0.59090906"
+ spreadMethod="pad" />
+ <linearGradient
+ xlink:href="#linearGradient1188"
+ id="linearGradient893"
+ x1="0.12793733"
+ y1="0.76923078"
+ x2="0.49608356"
+ y2="0.70850199" />
+ <linearGradient
+ xlink:href="#linearGradient855"
+ id="linearGradient625"
+ x1="0.035955057"
+ y1="1.0276498"
+ x2="0.053932585"
+ y2="-0.359447" />
+ <linearGradient
+ xlink:href="#linearGradient1188"
+ id="linearGradient627"
+ x1="1.2826855"
+ y1="0.12550607"
+ x2="-0.15547703"
+ y2="0.96356273" />
+ <radialGradient
+ xlink:href="#linearGradient1188"
+ id="radialGradient628"
+ r="1.5982224"
+ fy="0.4866707"
+ fx="0.36789617"
+ cy="0.4866707"
+ cx="0.36789617"
+ gradientTransform="scale(0.877379,1.139758)" />
+ <linearGradient
+ xlink:href="#linearGradient1188"
+ id="linearGradient628"
+ x1="0.76923078"
+ y1="0.14979757"
+ x2="0.41909814"
+ y2="0.73279351" />
+ </defs>
+ <sodipodi:namedview
+ id="namedview898"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.4732669"
+ inkscape:cx="50.051177"
+ inkscape:cy="18.096983"
+ inkscape:window-width="1022"
+ inkscape:window-height="670"
+ showguides="false"
+ snaptoguides="false"
+ showgrid="false"
+ snaptogrid="false"
+ inkscape:window-x="0"
+ inkscape:window-y="25">
+ <sodipodi:guide
+ orientation="vertical"
+ position="28.705556"
+ id="guide879" />
+ <sodipodi:guide
+ orientation="horizontal"
+ position="30.130655"
+ id="guide880" />
+ </sodipodi:namedview>
+ </defs>
+ <sodipodi:namedview
+ id="namedview1003"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.3368738"
+ inkscape:cx="24.541029"
+ inkscape:cy="14.368596"
+ inkscape:window-width="640"
+ inkscape:window-height="499"
+ showguides="true"
+ snaptoguides="true"
+ inkscape:window-x="138"
+ inkscape:window-y="169" />
+ <linearGradient
+ xlink:href="#linearGradient1060"
+ id="linearGradient1304"
+ x1="-0.20218579"
+ y1="0.21681416"
+ x2="0.67759562"
+ y2="0.57522124" />
+ <linearGradient
+ xlink:href="#linearGradient1065"
+ id="linearGradient1322"
+ x1="0.32404181"
+ y1="0.77876109"
+ x2="0.24041812"
+ y2="0.26548672" />
+ <defs
+ id="defs989">
+ <linearGradient
+ id="linearGradient850">
+ <stop
+ style="stop-color:#eed680;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop852" />
+ <stop
+ style="stop-color:#dfb546;stop-opacity:1.0000000;"
+ offset="0.68035328"
+ id="stop858" />
+ <stop
+ style="stop-color:#d8a429;stop-opacity:1.0000000;"
+ offset="0.77277374"
+ id="stop859" />
+ <stop
+ style="stop-color:#d1940c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop857" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient850"
+ id="linearGradient569"
+ x1="0.11875"
+ y1="0.12612613"
+ x2="0.59375"
+ y2="0.66066068"
+ spreadMethod="pad" />
+ <linearGradient
+ id="linearGradient839">
+ <stop
+ style="stop-color:#46a046;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop840" />
+ <stop
+ style="stop-color:#df421e;stop-opacity:1.0000000;"
+ offset="0.39364964"
+ id="stop841" />
+ <stop
+ style="stop-color:#ada7c8;stop-opacity:1.0000000;"
+ offset="0.72036445"
+ id="stop842" />
+ <stop
+ style="stop-color:#eed680;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop843" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient839"
+ id="linearGradient836"
+ x1="1.3267924e-17"
+ y1="0.5"
+ x2="1"
+ y2="0.5" />
+ <defs
+ id="defs604">
+ <linearGradient
+ id="linearGradient622">
+ <stop
+ style="stop-color:#f8e29d;stop-opacity:0.4471;"
+ offset="0"
+ id="stop623" />
+ <stop
+ style="stop-color:#272d2d;stop-opacity:0.4784;"
+ offset="1"
+ id="stop624" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient617">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop618" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop619" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient613">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.6235;"
+ offset="0"
+ id="stop614" />
+ <stop
+ style="stop-color:#5d6567;stop-opacity:1;"
+ offset="1"
+ id="stop615" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient607">
+ <stop
+ style="stop-color:#d7d5d5;stop-opacity:1;"
+ offset="0"
+ id="stop608" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.4039;"
+ offset="1"
+ id="stop609" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient607"
+ id="radialGradient610"
+ cx="1.4287461"
+ cy="0.75323397"
+ r="0.85534656"
+ fx="1.4287461"
+ fy="0.75323397"
+ gradientTransform="matrix(1,2.268336e-6,-1.975559e-5,1,5.713033e-8,3.856326e-8)" />
+ <linearGradient
+ xlink:href="#linearGradient617"
+ id="linearGradient612"
+ x1="7.7024956"
+ y1="-2.0263922"
+ x2="62.759903"
+ y2="56.137772"
+ gradientTransform="scale(0.9953779,1.0046436)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient613"
+ id="radialGradient616"
+ cx="58.70882"
+ cy="53.831562"
+ r="43.551846"
+ fx="58.70882"
+ fy="53.831562"
+ gradientTransform="scale(0.99517298,1.0048504)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ xlink:href="#linearGradient617"
+ id="linearGradient621" />
+ <linearGradient
+ xlink:href="#linearGradient617"
+ id="linearGradient626"
+ x1="72.060211"
+ y1="55.161442"
+ x2="32.409"
+ y2="12.126946"
+ gradientTransform="matrix(0.995134,-1.068631e-5,-1.31398e-7,1.00489,0,0)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ xlink:href="#linearGradient607"
+ id="linearGradient687"
+ x1="67.707405"
+ y1="49.314793"
+ x2="-10.031048"
+ y2="4.6068792"
+ gradientTransform="scale(0.99522839,1.0047945)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ xlink:href="#linearGradient617"
+ id="linearGradient742"
+ x1="-7.4378386"
+ y1="25.923714"
+ x2="18.009745"
+ y2="10.089797"
+ gradientTransform="scale(0.889853,1.123781)" />
+ </defs>
+ <sodipodi:namedview
+ id="namedview889"
+ showguides="true"
+ snaptoguides="true"
+ inkscape:zoom="7.5625000"
+ inkscape:cx="24.000000"
+ inkscape:cy="24.000000"
+ inkscape:window-width="640"
+ inkscape:window-height="496"
+ inkscape:window-x="0"
+ inkscape:window-y="26" />
+ </defs>
+ <sodipodi:namedview
+ id="namedview1023"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.5521067"
+ inkscape:cx="66.459318"
+ inkscape:cy="62.629296"
+ inkscape:window-width="1150"
+ inkscape:window-height="752"
+ showgrid="true"
+ snaptogrid="true"
+ inkscape:window-x="0"
+ inkscape:window-y="29">
+ <inkscape:grid
+ id="GridFromPre046Settings"
+ type="xygrid"
+ originx="0px"
+ originy="0px"
+ spacingx="1.0000000mm"
+ spacingy="1.0000000mm"
+ color="#0000ff"
+ empcolor="#0000ff"
+ opacity="0.2"
+ empopacity="0.4"
+ empspacing="5" />
+ </sodipodi:namedview>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1068"
+ id="linearGradient2924"
+ x1="41.673889"
+ y1="320.40921"
+ x2="36.082947"
+ y2="279.22458"
+ gradientTransform="matrix(2.3376099,0,0,0.29237422,-0.91913426,-1.5117091)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1068"
+ id="linearGradient2926"
+ x1="134.95444"
+ y1="108.16693"
+ x2="102.05431"
+ y2="71.835884"
+ gradientTransform="matrix(0.91680324,0,0,0.74547824,-0.91913426,-1.5117091)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1068"
+ id="linearGradient2928"
+ x1="145.32188"
+ y1="101.97199"
+ x2="129.22044"
+ y2="70.041069"
+ gradientTransform="matrix(0.90170536,0,0,0.75796032,-0.91913426,-1.5117091)"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <rect
+ style="fill-opacity:0.47154475;fill-rule:evenodd;stroke-width:3pt"
+ id="rect918"
+ width="48.72493"
+ height="42.16835"
+ ry="0.74231374"
+ x="67.536102"
+ y="66.474693"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.7811048" />
+ <rect
+ style="fill-opacity:0.47154475;fill-rule:evenodd;stroke-width:3pt"
+ id="rect1006"
+ width="63.211483"
+ height="54.705563"
+ ry="0.74231374"
+ x="64.47226"
+ y="30.558294"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.7811048" />
+ <rect
+ style="fill:#000000;fill-opacity:0.47058824;fill-rule:evenodd"
+ id="rect1005"
+ width="57.843418"
+ height="9.0050545"
+ ry="0.62889248"
+ x="65.398254"
+ y="82.153206"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.66175652" />
+ <rect
+ style="fill:url(#linearGradient2924);fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.82671446"
+ id="rect1007"
+ width="54.910637"
+ height="6.1445785"
+ ry="0.42912331"
+ x="64.622299"
+ y="82.282539"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.44939452" />
+ <rect
+ width="57.905403"
+ height="47.084496"
+ ry="1.7822117"
+ x="63.784973"
+ y="32.456562"
+ style="font-size:12px;fill:url(#linearGradient2926);fill-rule:evenodd;stroke:#000000;stroke-width:0"
+ id="rect1009"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="1.6336281" />
+ <rect
+ style="fill:#000000;fill-opacity:0.47058824;fill-rule:evenodd"
+ id="rect971"
+ width="44.58709"
+ height="6.9413123"
+ ry="0.62889248"
+ x="68.249886"
+ y="106.24529"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.66175652" />
+ <rect
+ width="64.637024"
+ height="54.068516"
+ ry="1.4120796"
+ x="59.853096"
+ y="28.740753"
+ style="font-size:12px;fill:url(#linearGradient2928);fill-rule:evenodd;stroke:#000000;stroke-width:1.65869105"
+ id="rect1008"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="1.3970968" />
+ <rect
+ width="51.129478"
+ height="39.964478"
+ ry="0.5422883"
+ x="66.358932"
+ y="34.89621"
+ style="font-size:12px;fill:#00b4ed;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.79054338;stroke-linejoin:round"
+ id="rect976"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.5422883" />
+ <metadata
+ id="metadata982">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <rect
+ width="44.634872"
+ height="36.293858"
+ ry="1.7822117"
+ x="67.006332"
+ y="67.937927"
+ style="font-size:12px;fill:url(#linearGradient1213);fill-rule:evenodd;stroke:#000000;stroke-width:0"
+ id="rect575"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="1.633628" />
+ <path
+ sodipodi:type="arc"
+ style="font-size:12px;fill:#444040;fill-opacity:0.47058824;fill-rule:evenodd"
+ id="path672"
+ d="m 68.473,57.85183 a 23.629898,3.2222576 0 1 1 -47.259797,0 23.629898,3.2222576 0 1 1 47.259797,0 z"
+ sodipodi:cx="44.843102"
+ sodipodi:cy="57.85183"
+ sodipodi:rx="23.629898"
+ sodipodi:ry="3.2222576"
+ transform="matrix(1.4221482,0,-0.30247168,1.9834766,9.6201687,-10.428817)"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big2.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998" />
+ <path
+ sodipodi:type="arc"
+ style="font-size:12px;fill:#4e4d4b;fill-rule:evenodd;stroke:#000000;stroke-width:2.30019999;stroke-opacity:0.9565"
+ id="path625"
+ d="m 58.291138,27.531645 a 19.367088,19.556963 0 1 1 -38.734177,0 19.367088,19.556963 0 1 1 38.734177,0 z"
+ sodipodi:cx="38.924049"
+ sodipodi:cy="27.531645"
+ sodipodi:rx="19.367088"
+ sodipodi:ry="19.556963"
+ transform="matrix(-1.0172416,-0.47376693,-0.5523759,1.3286212,116.84611,57.272851)"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big2.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998" />
+ <path
+ sodipodi:type="arc"
+ style="font-size:12px;fill:url(#linearGradient612);fill-rule:evenodd;stroke:#000000;stroke-width:2.06100011;stroke-opacity:0.9565"
+ id="path605"
+ d="m 58.291138,27.531645 a 19.367088,19.556963 0 1 1 -38.734177,0 19.367088,19.556963 0 1 1 38.734177,0 z"
+ sodipodi:cx="38.924049"
+ sodipodi:cy="27.531645"
+ sodipodi:rx="19.367088"
+ sodipodi:ry="19.556963"
+ transform="matrix(-1.4321234,-0.79696518,-1.1299666,2.2349846,128.07685,29.383033)"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big2.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998" />
+ <path
+ sodipodi:type="arc"
+ style="font-size:12px;fill:url(#radialGradient616);fill-rule:evenodd;stroke:#000000;stroke-width:0.317;stroke-opacity:0.97829997"
+ id="path606"
+ d="m 58.291138,27.531645 a 19.367088,19.556963 0 1 1 -38.734177,0 19.367088,19.556963 0 1 1 38.734177,0 z"
+ sodipodi:cx="38.924049"
+ sodipodi:cy="27.531645"
+ sodipodi:rx="19.367088"
+ sodipodi:ry="19.556963"
+ transform="matrix(-1.1546358,-0.69851175,-0.95634664,1.9588777,108.06887,31.115628)"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big2.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998" />
+ <path
+ sodipodi:type="arc"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:1.63499999"
+ id="path686"
+ d="m 58.291138,27.531645 a 19.367088,19.556963 0 1 1 -38.734177,0 19.367088,19.556963 0 1 1 38.734177,0 z"
+ sodipodi:cx="38.924049"
+ sodipodi:cy="27.531645"
+ sodipodi:rx="19.367088"
+ sodipodi:ry="19.556963"
+ transform="matrix(-0.39495459,-0.4546194,-0.52881207,0.94219495,73.198184,52.427791)"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big2.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998" />
+ <path
+ sodipodi:type="arc"
+ style="font-size:12px;fill:url(#linearGradient687);fill-rule:evenodd;stroke:#3f3b3b;stroke-width:0.77380002"
+ id="path611"
+ d="m 58.291138,27.531645 a 19.367088,19.556963 0 1 1 -38.734177,0 19.367088,19.556963 0 1 1 38.734177,0 z"
+ sodipodi:cx="38.924049"
+ sodipodi:cy="27.531645"
+ sodipodi:rx="19.367088"
+ sodipodi:ry="19.556963"
+ transform="matrix(-0.36949013,-0.40957751,-0.49471918,0.84885391,70.248021,52.066881)"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big2.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998" />
+ <rect
+ style="fill:url(#linearGradient573);fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.82671446"
+ id="rect934"
+ width="42.326431"
+ height="4.7363882"
+ ry="0.42912331"
+ x="67.651756"
+ y="106.34497"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.44939449" />
+ <rect
+ width="49.823761"
+ height="41.677303"
+ ry="1.4120796"
+ x="63.975548"
+ y="65.073692"
+ style="font-size:12px;fill:url(#linearGradient580);fill-rule:evenodd;stroke:#000000;stroke-width:1.27855873;stroke-opacity:1"
+ id="rect562"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="1.3970969" />
+ <rect
+ width="39.411831"
+ height="30.805574"
+ ry="0.5422883"
+ x="68.990387"
+ y="70.097008"
+ style="font-size:12px;fill:#003d88;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.61407685;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.93023257"
+ id="rect975"
+ inkscape:export-filename="/cowserver/documents/httpd/vhosts/images/mpd-big7.png"
+ inkscape:export-xdpi="721.66998"
+ inkscape:export-ydpi="721.66998"
+ rx="0.5422883" />
+</svg>
diff --git a/src/ArchiveDomain.cxx b/src/ArchiveDomain.cxx
deleted file mode 100644
index 2beeebcbb..000000000
--- a/src/ArchiveDomain.cxx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "ArchiveDomain.hxx"
-#include "util/Domain.hxx"
-
-const Domain archive_domain("archive");
diff --git a/src/ArchiveDomain.hxx b/src/ArchiveDomain.hxx
deleted file mode 100644
index 7bc8a223e..000000000
--- a/src/ArchiveDomain.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ARCHIVE_DOMAIN_HXX
-#define MPD_ARCHIVE_DOMAIN_HXX
-
-extern const class Domain archive_domain;
-
-#endif
diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx
deleted file mode 100644
index 4bdba62ab..000000000
--- a/src/ArchiveFile.hxx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ARCHIVE_FILE_HXX
-#define MPD_ARCHIVE_FILE_HXX
-
-class Mutex;
-class Cond;
-class Error;
-
-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 InputStream of a file within the archive.
- *
- * @param path the path within the archive
- */
- virtual InputStream *OpenStream(const char *path,
- Mutex &mutex, Cond &cond,
- Error &error) = 0;
-};
-
-#endif
diff --git a/src/ArchiveList.cxx b/src/ArchiveList.cxx
deleted file mode 100644
index f4a530506..000000000
--- a/src/ArchiveList.cxx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ArchiveList.hxx"
-#include "ArchivePlugin.hxx"
-#include "util/StringUtil.hxx"
-#include "archive/Bzip2ArchivePlugin.hxx"
-#include "archive/Iso9660ArchivePlugin.hxx"
-#include "archive/ZzipArchivePlugin.hxx"
-#include "util/Macros.hxx"
-
-#include <string.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
- nullptr
-};
-
-/** which plugins have been initialized successfully? */
-static bool archive_plugins_enabled[ARRAY_SIZE(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 == nullptr)
- return nullptr;
-
- archive_plugins_for_each_enabled(plugin)
- if (plugin->suffixes != nullptr &&
- string_array_contains(plugin->suffixes, suffix))
- return plugin;
-
- return nullptr;
-}
-
-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 nullptr;
-}
-
-void archive_plugin_init_all(void)
-{
- for (unsigned i = 0; archive_plugins[i] != nullptr; ++i) {
- const struct archive_plugin *plugin = archive_plugins[i];
- if (plugin->init == nullptr || archive_plugins[i]->init())
- archive_plugins_enabled[i] = true;
- }
-}
-
-void archive_plugin_deinit_all(void)
-{
- archive_plugins_for_each_enabled(plugin)
- if (plugin->finish != nullptr)
- plugin->finish();
-}
-
diff --git a/src/ArchiveList.hxx b/src/ArchiveList.hxx
deleted file mode 100644
index cbf159b2f..000000000
--- a/src/ArchiveList.hxx
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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) != nullptr; \
- ++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
deleted file mode 100644
index 7a93c136a..000000000
--- a/src/ArchiveLookup.cxx
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "ArchiveLookup.hxx"
-#include "ArchiveDomain.hxx"
-#include "Log.hxx"
-
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-gcc_pure
-static char *
-FindSlash(char *p, size_t i)
-{
- for (; i > 0; --i)
- if (p[i] == '/')
- return p + i;
-
- return nullptr;
-}
-
-gcc_pure
-static const char *
-FindSuffix(const char *p, const char *i)
-{
- for (; i > p; --i) {
- if (*i == '.')
- return i + 1;
- }
-
- return nullptr;
-}
-
-bool
-archive_lookup(char *pathname, const char **archive,
- const char **inpath, const char **suffix)
-{
- size_t idx = strlen(pathname);
-
- char *slash = nullptr;
-
- while (true) {
- //try to stat if its real directory
- struct stat st_info;
- if (stat(pathname, &st_info) == -1) {
- if (errno != ENOTDIR) {
- FormatErrno(archive_domain,
- "Failed to stat %s", pathname);
- return false;
- }
- } else {
- //is something found ins original path (is not an archive)
- if (slash == nullptr)
- return false;
-
- //its a file ?
- if (S_ISREG(st_info.st_mode)) {
- //so the upper should be file
- *archive = pathname;
- *inpath = slash + 1;
-
- //try to get suffix
- *suffix = FindSuffix(pathname, slash - 1);
- return true;
- } else {
- FormatError(archive_domain,
- "Not a regular file: %s",
- pathname);
- return false;
- }
- }
-
- //find one dir up
- if (slash != nullptr)
- *slash = '/';
-
- slash = FindSlash(pathname, idx - 1);
- if (slash == nullptr)
- return false;
-
- *slash = 0;
- idx = slash - pathname;
- }
-}
-
diff --git a/src/ArchiveLookup.hxx b/src/ArchiveLookup.hxx
deleted file mode 100644
index 0c4da9c93..000000000
--- a/src/ArchiveLookup.hxx
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ARCHIVE_LOOKUP_HXX
-#define MPD_ARCHIVE_LOOKUP_HXX
-
-/**
- *
- * 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, const char **archive,
- const char **inpath, const char **suffix);
-
-#endif
-
diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx
deleted file mode 100644
index 05085fb33..000000000
--- a/src/ArchivePlugin.cxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "util/Error.hxx"
-
-#include <assert.h>
-
-ArchiveFile *
-archive_file_open(const struct archive_plugin *plugin, const char *path,
- Error &error)
-{
- assert(plugin != nullptr);
- assert(plugin->open != nullptr);
- assert(path != nullptr);
-
- ArchiveFile *file = plugin->open(path, error);
- assert((file == nullptr) == error.IsDefined());
-
- return file;
-}
diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx
deleted file mode 100644
index 6439c7242..000000000
--- a/src/ArchivePlugin.hxx
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ARCHIVE_PLUGIN_HXX
-#define MPD_ARCHIVE_PLUGIN_HXX
-
-struct InputStream;
-class ArchiveFile;
-class ArchiveVisitor;
-class Error;
-
-struct archive_plugin {
- const char *name;
-
- /**
- * optional, set this to nullptr 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 nullptr 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 nullptr when opening fails
- */
- ArchiveFile *(*open)(const char *path_fs, Error &error);
-
- /**
- * suffixes handled by this plugin.
- * last element in these arrays must always be a nullptr
- */
- const char *const*suffixes;
-};
-
-ArchiveFile *
-archive_file_open(const struct archive_plugin *plugin, const char *path,
- Error &error);
-
-#endif
diff --git a/src/ArchiveVisitor.hxx b/src/ArchiveVisitor.hxx
deleted file mode 100644
index e951cb5e9..000000000
--- a/src/ArchiveVisitor.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ARCHIVE_VISITOR_HXX
-#define MPD_ARCHIVE_VISITOR_HXX
-
-class ArchiveVisitor {
-public:
- virtual void VisitArchiveEntry(const char *path_utf8) = 0;
-};
-
-#endif
diff --git a/src/AudioConfig.cxx b/src/AudioConfig.cxx
index a8fc7aab3..d54f59e17 100644
--- a/src/AudioConfig.cxx
+++ b/src/AudioConfig.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,9 +21,9 @@
#include "AudioConfig.hxx"
#include "AudioFormat.hxx"
#include "AudioParser.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
#include "util/Error.hxx"
#include "system/FatalError.hxx"
diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx
index ebe202974..471e60e51 100644
--- a/src/AudioConfig.hxx
+++ b/src/AudioConfig.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/AudioFormat.cxx b/src/AudioFormat.cxx
index 04636c1e2..edfb9d8fe 100644
--- a/src/AudioFormat.cxx
+++ b/src/AudioFormat.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/AudioFormat.hxx b/src/AudioFormat.hxx
index fae7625ea..0937ab8ae 100644
--- a/src/AudioFormat.hxx
+++ b/src/AudioFormat.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/AudioParser.cxx b/src/AudioParser.cxx
index a21dcafa7..74bb04abc 100644
--- a/src/AudioParser.cxx
+++ b/src/AudioParser.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx
index cb6eb3ca0..07ad7cb4a 100644
--- a/src/AudioParser.hxx
+++ b/src/AudioParser.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/AvahiPoll.cxx b/src/AvahiPoll.cxx
deleted file mode 100644
index 0d5a43dad..000000000
--- a/src/AvahiPoll.cxx
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AvahiPoll.hxx"
-#include "event/Loop.hxx"
-#include "event/SocketMonitor.hxx"
-#include "event/TimeoutMonitor.hxx"
-
-static unsigned
-FromAvahiWatchEvent(AvahiWatchEvent e)
-{
- return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) |
- (e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) |
- (e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) |
- (e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0);
-}
-
-static AvahiWatchEvent
-ToAvahiWatchEvent(unsigned e)
-{
- return AvahiWatchEvent((e & SocketMonitor::READ ? AVAHI_WATCH_IN : 0) |
- (e & SocketMonitor::WRITE ? AVAHI_WATCH_OUT : 0) |
- (e & SocketMonitor::ERROR ? AVAHI_WATCH_ERR : 0) |
- (e & SocketMonitor::HANGUP ? AVAHI_WATCH_HUP : 0));
-}
-
-struct AvahiWatch final : private SocketMonitor {
-private:
- const AvahiWatchCallback callback;
- void *const userdata;
-
- AvahiWatchEvent received;
-
-public:
- AvahiWatch(int _fd, AvahiWatchEvent _event,
- AvahiWatchCallback _callback, void *_userdata,
- EventLoop &_loop)
- :SocketMonitor(_fd, _loop),
- callback(_callback), userdata(_userdata),
- received(AvahiWatchEvent(0)) {
- Schedule(FromAvahiWatchEvent(_event));
- }
-
- ~AvahiWatch() {
- Steal();
- }
-
- static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) {
- w->Schedule(FromAvahiWatchEvent(event));
- }
-
- static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) {
- return w->received;
- }
-
- static void WatchFree(AvahiWatch *w) {
- delete w;
- }
-
-protected:
- virtual bool OnSocketReady(unsigned flags) {
- received = ToAvahiWatchEvent(flags);
- callback(this, Get(), received, userdata);
- received = AvahiWatchEvent(0);
- return true;
- }
-};
-
-static constexpr unsigned
-TimevalToMS(const timeval &tv)
-{
- return tv.tv_sec * 1000 + (tv.tv_usec + 500) / 1000;
-}
-
-struct AvahiTimeout final : private TimeoutMonitor {
-private:
- const AvahiTimeoutCallback callback;
- void *const userdata;
-
-public:
- AvahiTimeout(const struct timeval *tv,
- AvahiTimeoutCallback _callback, void *_userdata,
- EventLoop &_loop)
- :TimeoutMonitor(_loop),
- callback(_callback), userdata(_userdata) {
- if (tv != nullptr)
- Schedule(TimevalToMS(*tv));
- }
-
- static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) {
- if (tv != nullptr)
- t->Schedule(TimevalToMS(*tv));
- else
- t->Cancel();
- }
-
- static void TimeoutFree(AvahiTimeout *t) {
- delete t;
- }
-
-protected:
- virtual void OnTimeout() {
- callback(this, userdata);
- }
-};
-
-MyAvahiPoll::MyAvahiPoll(EventLoop &_loop):event_loop(_loop)
-{
- watch_new = WatchNew;
- watch_update = AvahiWatch::WatchUpdate;
- watch_get_events = AvahiWatch::WatchGetEvents;
- watch_free = AvahiWatch::WatchFree;
- timeout_new = TimeoutNew;
- timeout_update = AvahiTimeout::TimeoutUpdate;
- timeout_free = AvahiTimeout::TimeoutFree;
-}
-
-AvahiWatch *
-MyAvahiPoll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event,
- AvahiWatchCallback callback, void *userdata) {
- const MyAvahiPoll &poll = *(const MyAvahiPoll *)api;
-
- return new AvahiWatch(fd, event, callback, userdata,
- poll.event_loop);
-}
-
-AvahiTimeout *
-MyAvahiPoll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv,
- AvahiTimeoutCallback callback, void *userdata) {
- const MyAvahiPoll &poll = *(const MyAvahiPoll *)api;
-
- return new AvahiTimeout(tv, callback, userdata,
- poll.event_loop);
-}
diff --git a/src/AvahiPoll.hxx b/src/AvahiPoll.hxx
deleted file mode 100644
index 850205c46..000000000
--- a/src/AvahiPoll.hxx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_AVAHI_POLL_HXX
-#define MPD_AVAHI_POLL_HXX
-
-#include "check.h"
-#include "Compiler.h"
-
-#include <avahi-common/watch.h>
-
-class EventLoop;
-
-class MyAvahiPoll final : public AvahiPoll {
- EventLoop &event_loop;
-
-public:
- MyAvahiPoll(EventLoop &_loop);
-
-private:
- static AvahiWatch *WatchNew(const AvahiPoll *api, int fd,
- AvahiWatchEvent event,
- AvahiWatchCallback callback,
- void *userdata);
-
- static AvahiTimeout *TimeoutNew(const AvahiPoll *api,
- const struct timeval *tv,
- AvahiTimeoutCallback callback,
- void *userdata);
-};
-
-#endif
diff --git a/src/CheckAudioFormat.cxx b/src/CheckAudioFormat.cxx
index 1f3ef4925..03e67e07e 100644
--- a/src/CheckAudioFormat.cxx
+++ b/src/CheckAudioFormat.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/CheckAudioFormat.hxx b/src/CheckAudioFormat.hxx
index df952adc7..67bd88a61 100644
--- a/src/CheckAudioFormat.hxx
+++ b/src/CheckAudioFormat.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/Chrono.hxx b/src/Chrono.hxx
new file mode 100644
index 000000000..cc87c5ba1
--- /dev/null
+++ b/src/Chrono.hxx
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CHRONO_HXX
+#define MPD_CHRONO_HXX
+
+#include "Compiler.h"
+
+#include <chrono>
+#include <utility>
+#include <cstdint>
+
+#if defined(__GNUC__) && !GCC_CHECK_VERSION(4,7) && !defined(__clang__)
+/* std::chrono::duration operators are "constexpr" since gcc 4.7 */
+#define chrono_constexpr gcc_pure
+#else
+#define chrono_constexpr constexpr
+#endif
+
+/**
+ * A time stamp within a song. Granularity is 1 millisecond and the
+ * maximum value is about 49 days.
+ */
+class SongTime : public std::chrono::duration<std::uint32_t, std::milli> {
+ typedef std::chrono::duration<std::uint32_t, std::milli> Base;
+ typedef Base::rep rep;
+
+public:
+ SongTime() = default;
+
+ template<typename T>
+ explicit constexpr SongTime(T t):Base(t) {}
+
+ static constexpr SongTime zero() {
+ return SongTime(Base::zero());
+ }
+
+ static constexpr SongTime FromS(unsigned s) {
+ return SongTime(rep(s) * 1000);
+ }
+
+ static constexpr SongTime FromS(float s) {
+ return SongTime(rep(s * 1000));
+ }
+
+ static constexpr SongTime FromS(double s) {
+ return SongTime(rep(s * 1000));
+ }
+
+ static constexpr SongTime FromMS(rep ms) {
+ return SongTime(ms);
+ }
+
+ constexpr rep ToS() const {
+ return count() / rep(1000);
+ }
+
+ constexpr rep RoundS() const {
+ return (count() + 500) / rep(1000);
+ }
+
+ constexpr rep ToMS() const {
+ return count();
+ }
+
+ template<typename T=rep>
+ constexpr T ToScale(unsigned scale) const {
+ return count() * T(scale) / 1000;
+ }
+
+ /**
+ * Convert a scalar value with the given scale to a #SongTime
+ * instance.
+ *
+ * @param value the input value
+ * @param scale the value's scale in Hz
+ */
+ template<typename T=rep>
+ static constexpr SongTime FromScale(T value, unsigned scale) {
+ return SongTime(value * T(1000) / T(scale));
+ }
+
+ constexpr double ToDoubleS() const {
+ return double(count()) / 1000.;
+ };
+
+ constexpr bool IsZero() const {
+ return count() == 0;
+ }
+
+ constexpr bool IsPositive() const {
+ return count() > 0;
+ }
+
+ chrono_constexpr SongTime operator+(const SongTime &other) const {
+ return SongTime(*(const Base *)this + (const Base &)other);
+ }
+
+ chrono_constexpr SongTime operator-(const SongTime &other) const {
+ return SongTime(*(const Base *)this - (const Base &)other);
+ }
+};
+
+/**
+ * A variant of #SongTime that is based on a signed integer. It can
+ * be used for relative values.
+ */
+class SignedSongTime : public std::chrono::duration<std::int32_t, std::milli> {
+ typedef std::chrono::duration<std::int32_t, std::milli> Base;
+ typedef Base::rep rep;
+
+public:
+ SignedSongTime() = default;
+
+ template<typename T>
+ explicit constexpr SignedSongTime(T t):Base(t) {}
+
+ /**
+ * Allow implicit conversion from SongTime to SignedSongTime.
+ */
+ constexpr SignedSongTime(SongTime t):Base(t) {}
+
+ static constexpr SignedSongTime zero() {
+ return SignedSongTime(Base::zero());
+ }
+
+ /**
+ * Generate a negative value.
+ */
+ static constexpr SignedSongTime Negative() {
+ return SignedSongTime(-1);
+ }
+
+ static constexpr SignedSongTime FromS(int s) {
+ return SignedSongTime(rep(s) * 1000);
+ }
+
+ static constexpr SignedSongTime FromS(unsigned s) {
+ return SignedSongTime(rep(s) * 1000);
+ }
+
+ static constexpr SignedSongTime FromS(float s) {
+ return SignedSongTime(rep(s * 1000));
+ }
+
+ static constexpr SignedSongTime FromS(double s) {
+ return SignedSongTime(rep(s * 1000));
+ }
+
+ static constexpr SignedSongTime FromMS(rep ms) {
+ return SignedSongTime(ms);
+ }
+
+ constexpr rep ToS() const {
+ return count() / rep(1000);
+ }
+
+ constexpr rep RoundS() const {
+ return (count() + 500) / rep(1000);
+ }
+
+ constexpr rep ToMS() const {
+ return count();
+ }
+
+ template<typename T=rep>
+ constexpr T ToScale(unsigned scale) const {
+ return count() * T(scale) / 1000;
+ }
+
+ /**
+ * Convert a scalar value with the given scale to a
+ * #SignedSongTime instance.
+ *
+ * @param value the input value
+ * @param scale the value's scale in Hz
+ */
+ template<typename T=rep>
+ static constexpr SignedSongTime FromScale(T value, unsigned scale) {
+ return SignedSongTime(value * T(1000) / T(scale));
+ }
+
+ constexpr double ToDoubleS() const {
+ return double(count()) / 1000.;
+ };
+
+ constexpr bool IsZero() const {
+ return count() == 0;
+ }
+
+ constexpr bool IsPositive() const {
+ return count() > 0;
+ }
+
+ constexpr bool IsNegative() const {
+ return count() < 0;
+ }
+
+ chrono_constexpr SignedSongTime operator+(const SignedSongTime &other) const {
+ return SignedSongTime(*(const Base *)this + (const Base &)other);
+ }
+
+ chrono_constexpr SignedSongTime operator-(const SignedSongTime &other) const {
+ return SignedSongTime(*(const Base *)this - (const Base &)other);
+ }
+};
+
+#undef chrono_constexpr
+
+#endif
diff --git a/src/Client.cxx b/src/Client.cxx
deleted file mode 100644
index 928747897..000000000
--- a/src/Client.cxx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "util/Domain.hxx"
-
-const Domain client_domain("client");
diff --git a/src/Client.hxx b/src/Client.hxx
deleted file mode 100644
index fd81b59e0..000000000
--- a/src/Client.hxx
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CLIENT_H
-#define MPD_CLIENT_H
-
-#include "check.h"
-#include "ClientMessage.hxx"
-#include "command/CommandListBuilder.hxx"
-#include "event/FullyBufferedSocket.hxx"
-#include "event/TimeoutMonitor.hxx"
-#include "Compiler.h"
-
-#include <set>
-#include <string>
-#include <list>
-
-#include <stddef.h>
-#include <stdarg.h>
-
-struct sockaddr;
-class EventLoop;
-struct Partition;
-
-class Client final : private FullyBufferedSocket, TimeoutMonitor {
-public:
- Partition &partition;
- struct playlist &playlist;
- struct PlayerControl &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 IsExpired() const {
- return !FullyBufferedSocket::IsDefined();
- }
-
- void Close();
- void SetExpired();
-
- using FullyBufferedSocket::Write;
-
- /**
- * returns the uid of the client process, or a negative value
- * if the uid is unknown
- */
- int GetUID() const {
- return uid;
- }
-
- /**
- * Is this client running on the same machine, connected with
- * a local (UNIX domain) socket?
- */
- bool IsLocal() const {
- return uid >= 0;
- }
-
- unsigned GetPermission() const {
- return permission;
- }
-
- void SetPermission(unsigned _permission) {
- permission = _permission;
- }
-
- /**
- * Send "idle" response to this client.
- */
- void IdleNotify();
- void IdleAdd(unsigned flags);
- bool IdleWait(unsigned flags);
-
- enum class SubscribeResult {
- /** success */
- OK,
-
- /** invalid channel name */
- INVALID,
-
- /** already subscribed to this channel */
- ALREADY,
-
- /** too many subscriptions */
- FULL,
- };
-
- gcc_pure
- bool IsSubscribed(const char *channel_name) const {
- return subscriptions.find(channel_name) != subscriptions.end();
- }
-
- SubscribeResult Subscribe(const char *channel);
- bool Unsubscribe(const char *channel);
- void UnsubscribeAll();
- bool PushMessage(const ClientMessage &msg);
-
-private:
- /* virtual methods from class BufferedSocket */
- virtual InputResult OnSocketInput(void *data, size_t length) override;
- virtual void OnSocketError(Error &&error) override;
- virtual void OnSocketClosed() override;
-
- /* virtual methods from class TimeoutMonitor */
- virtual void OnTimeout() override;
-};
-
-void client_manager_init(void);
-
-void
-client_new(EventLoop &loop, Partition &partition,
- int fd, const struct sockaddr *sa, size_t sa_length, int uid);
-
-/**
- * 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_printf(2,3)
-void
-client_printf(Client &client, const char *fmt, ...);
-
-#endif
diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx
deleted file mode 100644
index 6c86057a1..000000000
--- a/src/ClientEvent.cxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-void
-Client::OnSocketError(Error &&error)
-{
- FormatError(error, "error on client %d", num);
-
- SetExpired();
-}
-
-void
-Client::OnSocketClosed()
-{
- SetExpired();
-}
diff --git a/src/ClientExpire.cxx b/src/ClientExpire.cxx
deleted file mode 100644
index 5954bba8b..000000000
--- a/src/ClientExpire.cxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "Log.hxx"
-
-void
-Client::SetExpired()
-{
- if (IsExpired())
- return;
-
- FullyBufferedSocket::Close();
- TimeoutMonitor::Schedule(0);
-}
-
-void
-Client::OnTimeout()
-{
- if (!IsExpired()) {
- assert(!idle_waiting);
- FormatDebug(client_domain, "[%u] timeout", num);
- }
-
- Close();
-}
diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx
deleted file mode 100644
index 7a5dd37a6..000000000
--- a/src/ClientFile.cxx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientFile.hxx"
-#include "Client.hxx"
-#include "protocol/Ack.hxx"
-#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <unistd.h>
-
-bool
-client_allow_file(const Client &client, Path path_fs, Error &error)
-{
-#ifdef WIN32
- (void)client;
- (void)path_fs;
-
- error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
- return false;
-#else
- const int uid = client.GetUID();
- if (uid >= 0 && (uid_t)uid == geteuid())
- /* always allow access if user runs his own MPD
- instance */
- return true;
-
- if (uid < 0) {
- /* unauthenticated client */
- error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
- return false;
- }
-
- struct stat st;
- if (!StatFile(path_fs, st)) {
- error.SetErrno();
- return false;
- }
-
- if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
- /* client is not owner */
- error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
- return false;
- }
-
- return true;
-#endif
-}
diff --git a/src/ClientFile.hxx b/src/ClientFile.hxx
deleted file mode 100644
index b06fbf212..000000000
--- a/src/ClientFile.hxx
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CLIENT_FILE_HXX
-#define MPD_CLIENT_FILE_HXX
-
-class Client;
-class Path;
-class Error;
-
-/**
- * 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, Path path_fs, Error &error);
-
-#endif
diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx
deleted file mode 100644
index e79f3430b..000000000
--- a/src/ClientGlobal.cxx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "ClientList.hxx"
-#include "ConfigGlobal.hxx"
-
-#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
deleted file mode 100644
index f9778a645..000000000
--- a/src/ClientIdle.cxx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
deleted file mode 100644
index c64310093..000000000
--- a/src/ClientInternal.hxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CLIENT_INTERNAL_HXX
-#define MPD_CLIENT_INTERNAL_HXX
-
-#include "check.h"
-#include "Client.hxx"
-#include "command/CommandResult.hxx"
-
-static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16;
-static constexpr unsigned CLIENT_MAX_MESSAGES = 64;
-
-extern const class Domain client_domain;
-
-extern int client_timeout;
-extern size_t client_max_command_list_size;
-extern size_t client_max_output_buffer_size;
-
-CommandResult
-client_process_line(Client &client, char *line);
-
-#endif
diff --git a/src/ClientList.cxx b/src/ClientList.cxx
deleted file mode 100644
index 37e6f1289..000000000
--- a/src/ClientList.cxx
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
deleted file mode 100644
index 8c6b15755..000000000
--- a/src/ClientList.hxx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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) {}
- ~ClientList() {
- CloseAll();
- }
-
- 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
deleted file mode 100644
index 4b124c6ca..000000000
--- a/src/ClientMessage.cxx
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "ClientMessage.hxx"
-#include "util/CharUtil.hxx"
-#include "Compiler.h"
-
-gcc_const
-static bool
-valid_channel_char(const char ch)
-{
- return IsAlphaNumericASCII(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
deleted file mode 100644
index 4579e523e..000000000
--- a/src/ClientMessage.hxx
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CLIENT_MESSAGE_H
-#define MPD_CLIENT_MESSAGE_H
-
-#include "Compiler.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
deleted file mode 100644
index e84887072..000000000
--- a/src/ClientNew.cxx
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "ClientList.hxx"
-#include "Partition.hxx"
-#include "Instance.hxx"
-#include "system/fd_util.h"
-#include "system/Resolver.hxx"
-#include "Permission.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <sys/types.h>
-#ifdef WIN32
-#include <winsock2.h>
-#else
-#include <sys/socket.h>
-#endif
-#include <unistd.h>
-
-#ifdef HAVE_LIBWRAP
-#include <tcpd.h>
-#endif
-
-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,
- IgnoreError());
- 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 */
- FormatWarning(client_domain,
- "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()) {
- LogWarning(client_domain, "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, IgnoreError());
- FormatInfo(client_domain, "[%u] opened from %s", client->num, remote);
- g_free(remote);
-}
-
-void
-Client::Close()
-{
- partition.instance.client_list->Remove(*this);
-
- SetExpired();
-
- FormatInfo(client_domain, "[%u] closed", num);
- delete this;
-}
diff --git a/src/ClientProcess.cxx b/src/ClientProcess.cxx
deleted file mode 100644
index 485e687c9..000000000
--- a/src/ClientProcess.cxx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "protocol/Result.hxx"
-#include "command/AllCommands.hxx"
-#include "Log.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 CommandResult
-client_process_command_list(Client &client, bool list_ok,
- std::list<std::string> &&list)
-{
- CommandResult ret = CommandResult::OK;
- unsigned num = 0;
-
- for (auto &&i : list) {
- char *cmd = &*i.begin();
-
- FormatDebug(client_domain, "process command \"%s\"", cmd);
- ret = command_process(client, num++, cmd);
- FormatDebug(client_domain, "command returned %i", ret);
- if (ret != CommandResult::OK || client.IsExpired())
- break;
- else if (list_ok)
- client_puts(client, "list_OK\n");
- }
-
- return ret;
-}
-
-CommandResult
-client_process_line(Client &client, char *line)
-{
- CommandResult 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 CommandResult::OK;
- } else if (client.idle_waiting) {
- /* during idle mode, clients must not send anything
- except "noidle" */
- FormatWarning(client_domain,
- "[%u] command \"%s\" during idle",
- client.num, line);
- return CommandResult::CLOSE;
- }
-
- if (client.cmd_list.IsActive()) {
- if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
- FormatDebug(client_domain,
- "[%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));
- FormatDebug(client_domain,
- "[%u] process command "
- "list returned %i", client.num, ret);
-
- if (ret == CommandResult::CLOSE ||
- client.IsExpired())
- return CommandResult::CLOSE;
-
- if (ret == CommandResult::OK)
- command_success(client);
-
- client.cmd_list.Reset();
- } else {
- if (!client.cmd_list.Add(line)) {
- FormatWarning(client_domain,
- "[%u] command list size "
- "is larger than the max (%lu)",
- client.num,
- (unsigned long)client_max_command_list_size);
- return CommandResult::CLOSE;
- }
-
- ret = CommandResult::OK;
- }
- } else {
- if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
- client.cmd_list.Begin(false);
- ret = CommandResult::OK;
- } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
- client.cmd_list.Begin(true);
- ret = CommandResult::OK;
- } else {
- FormatDebug(client_domain,
- "[%u] process command \"%s\"",
- client.num, line);
- ret = command_process(client, 0, line);
- FormatDebug(client_domain,
- "[%u] command returned %i",
- client.num, ret);
-
- if (ret == CommandResult::CLOSE ||
- client.IsExpired())
- return CommandResult::CLOSE;
-
- if (ret == CommandResult::OK)
- command_success(client);
- }
- }
-
- return ret;
-}
diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx
deleted file mode 100644
index 22edefe60..000000000
--- a/src/ClientRead.cxx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "Main.hxx"
-#include "event/Loop.hxx"
-#include "util/CharUtil.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-BufferedSocket::InputResult
-Client::OnSocketInput(void *data, size_t length)
-{
- char *p = (char *)data;
- char *newline = (char *)memchr(p, '\n', length);
- if (newline == nullptr)
- return InputResult::MORE;
-
- TimeoutMonitor::ScheduleSeconds(client_timeout);
-
- BufferedSocket::ConsumeInput(newline + 1 - p);
-
- /* skip whitespace at the end of the line */
- while (newline > p && IsWhitespaceOrNull(newline[-1]))
- --newline;
-
- /* terminate the string at the end of the line */
- *newline = 0;
-
- CommandResult result = client_process_line(*this, p);
- switch (result) {
- case CommandResult::OK:
- case CommandResult::IDLE:
- case CommandResult::ERROR:
- break;
-
- case CommandResult::KILL:
- Close();
- main_loop->Break();
- return InputResult::CLOSED;
-
- case CommandResult::FINISH:
- if (Flush())
- Close();
- return InputResult::CLOSED;
-
- case CommandResult::CLOSE:
- Close();
- return InputResult::CLOSED;
- }
-
- if (IsExpired()) {
- Close();
- return InputResult::CLOSED;
- }
-
- return InputResult::AGAIN;
-}
diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx
deleted file mode 100644
index 3a9f1b19c..000000000
--- a/src/ClientSubscribe.cxx
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "Idle.hxx"
-
-#include <assert.h>
-#include <string.h>
-
- bool Unsubscribe(const char *channel);
- void UnsubscribeAll();
- bool PushMessage(const ClientMessage &msg);
-
-Client::SubscribeResult
-Client::Subscribe(const char *channel)
-{
- assert(channel != nullptr);
-
- if (!client_message_valid_channel_name(channel))
- return Client::SubscribeResult::INVALID;
-
- if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
- return Client::SubscribeResult::FULL;
-
- auto r = subscriptions.insert(channel);
- if (!r.second)
- return Client::SubscribeResult::ALREADY;
-
- ++num_subscriptions;
-
- idle_add(IDLE_SUBSCRIPTION);
-
- return Client::SubscribeResult::OK;
-}
-
-bool
-Client::Unsubscribe(const char *channel)
-{
- const auto i = subscriptions.find(channel);
- if (i == subscriptions.end())
- return false;
-
- assert(num_subscriptions > 0);
-
- subscriptions.erase(i);
- --num_subscriptions;
-
- idle_add(IDLE_SUBSCRIPTION);
-
- assert((num_subscriptions == 0) ==
- subscriptions.empty());
-
- return true;
-}
-
-void
-Client::UnsubscribeAll()
-{
- subscriptions.clear();
- num_subscriptions = 0;
-}
-
-bool
-Client::PushMessage(const ClientMessage &msg)
-{
- if (messages.size() >= CLIENT_MAX_MESSAGES ||
- !IsSubscribed(msg.GetChannel()))
- return false;
-
- if (messages.empty())
- IdleAdd(IDLE_MESSAGE);
-
- messages.push_back(msg);
- return true;
-}
diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx
deleted file mode 100644
index e66197eb7..000000000
--- a/src/ClientWrite.cxx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ClientInternal.hxx"
-#include "util/FormatString.hxx"
-
-#include <string.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)
-{
- char *p = FormatNewV(fmt, args);
- client_write(client, p, strlen(p));
- delete[] p;
-}
-
-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/CommandLine.cxx b/src/CommandLine.cxx
index 4bed5d531..c6e9c69c5 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -22,57 +22,115 @@
#include "ls.hxx"
#include "LogInit.hxx"
#include "Log.hxx"
-#include "ConfigGlobal.hxx"
-#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 "config/ConfigGlobal.hxx"
+#include "decoder/DecoderList.hxx"
+#include "decoder/DecoderPlugin.hxx"
+#include "output/Registry.hxx"
+#include "output/OutputPlugin.hxx"
+#include "input/Registry.hxx"
+#include "input/InputPlugin.hxx"
+#include "playlist/PlaylistRegistry.hxx"
+#include "playlist/PlaylistPlugin.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
+#include "fs/StandardDirectory.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
+#include "util/OptionDef.hxx"
+#include "util/OptionParser.hxx"
+
+#ifdef ENABLE_DATABASE
+#include "db/Registry.hxx"
+#include "db/DatabasePlugin.hxx"
+#include "storage/Registry.hxx"
+#include "storage/StoragePlugin.hxx"
+#endif
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+#include "neighbor/Registry.hxx"
+#include "neighbor/NeighborPlugin.hxx"
+#endif
#ifdef ENABLE_ENCODER
-#include "EncoderList.hxx"
-#include "EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "encoder/EncoderPlugin.hxx"
#endif
#ifdef ENABLE_ARCHIVE
-#include "ArchiveList.hxx"
-#include "ArchivePlugin.hxx"
+#include "archive/ArchiveList.hxx"
+#include "archive/ArchivePlugin.hxx"
#endif
-#include <glib.h>
-
#include <stdio.h>
#include <stdlib.h>
#ifdef WIN32
-#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
+#define CONFIG_FILE_LOCATION "mpd\\mpd.conf"
+#define APP_CONFIG_FILE_LOCATION "conf\\mpd.conf"
#else
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
#define USER_CONFIG_FILE_LOCATION_XDG "mpd/mpd.conf"
#endif
+static constexpr OptionDef opt_kill(
+ "kill", "kill the currently running mpd session");
+static constexpr OptionDef opt_no_config(
+ "no-config", "don't read from config");
+static constexpr OptionDef opt_no_daemon(
+ "no-daemon", "don't detach from console");
+static constexpr OptionDef opt_stdout(
+ "stdout", nullptr); // hidden, compatibility with old versions
+static constexpr OptionDef opt_stderr(
+ "stderr", "print messages to stderr");
+static constexpr OptionDef opt_verbose(
+ "verbose", 'v', "verbose logging");
+static constexpr OptionDef opt_version(
+ "version", 'V', "print version number");
+static constexpr OptionDef opt_help(
+ "help", 'h', "show help options");
+static constexpr OptionDef opt_help_alt(
+ nullptr, '?', nullptr); // hidden, standard alias for --help
+
static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn
static void version(void)
{
- puts("Music Player Daemon " VERSION "\n"
+ puts("Music Player Daemon " VERSION
+#ifdef GIT_COMMIT
+ " (" GIT_COMMIT ")"
+#endif
+ "\n"
"\n"
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
"Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n"
"This is free software; see the source for copying conditions. There is NO\n"
- "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
- "\n"
+ "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
+
+#ifdef ENABLE_DATABASE
+ puts("\n"
+ "Database plugins:");
+
+ for (auto i = database_plugins; *i != nullptr; ++i)
+ printf(" %s", (*i)->name);
+
+ puts("\n\n"
+ "Storage plugins:");
+
+ for (auto i = storage_plugins; *i != nullptr; ++i)
+ printf(" %s", (*i)->name);
+#endif
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ puts("\n\n"
+ "Neighbor plugins:");
+ for (auto i = neighbor_plugins; *i != nullptr; ++i)
+ printf(" %s", (*i)->name);
+#endif
+
+ puts("\n\n"
"Decoders plugins:");
decoder_plugins_for_each([](const DecoderPlugin &plugin){
@@ -132,122 +190,165 @@ static void version(void)
exit(EXIT_SUCCESS);
}
-static const char *summary =
- "Music Player Daemon - a daemon for playing music.";
+static void PrintOption(const OptionDef &opt)
+{
+ if (opt.HasShortOption())
+ printf(" -%c, --%-12s%s\n",
+ opt.GetShortOption(),
+ opt.GetLongOption(),
+ opt.GetDescription());
+ else
+ printf(" --%-16s%s\n",
+ opt.GetLongOption(),
+ opt.GetDescription());
+}
-gcc_pure
-static AllocatedPath
-PathBuildChecked(const AllocatedPath &a, PathTraits::const_pointer b)
+gcc_noreturn
+static void help(void)
{
- if (a.IsNull())
- return AllocatedPath::Null();
+ puts("Usage:\n"
+ " mpd [OPTION...] [path/to/mpd.conf]\n"
+ "\n"
+ "Music Player Daemon - a daemon for playing music.\n"
+ "\n"
+ "Options:");
+
+ PrintOption(opt_help);
+ PrintOption(opt_kill);
+ PrintOption(opt_no_config);
+ PrintOption(opt_no_daemon);
+ PrintOption(opt_stderr);
+ PrintOption(opt_verbose);
+ PrintOption(opt_version);
- return AllocatedPath::Build(a, b);
+ exit(EXIT_SUCCESS);
+}
+
+class ConfigLoader
+{
+ Error &error;
+ bool result;
+public:
+ ConfigLoader(Error &_error) : error(_error), result(false) { }
+
+ bool GetResult() const { return result; }
+
+ bool TryFile(const Path path);
+ bool TryFile(const AllocatedPath &base_path,
+ PathTraitsFS::const_pointer path);
+};
+
+bool ConfigLoader::TryFile(Path path)
+{
+ if (FileExists(path)) {
+ result = ReadConfigFile(path, error);
+ return true;
+ }
+ return false;
+}
+
+bool ConfigLoader::TryFile(const AllocatedPath &base_path,
+ PathTraitsFS::const_pointer path)
+{
+ if (base_path.IsNull())
+ return false;
+ auto full_path = AllocatedPath::Build(base_path, path);
+ return TryFile(full_path);
}
bool
parse_cmdline(int argc, char **argv, struct options *options,
Error &error)
{
- GOptionContext *context;
- bool ret;
- static gboolean option_version,
- option_no_daemon,
- option_no_config;
- const GOptionEntry entries[] = {
- { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
- "kill the currently running mpd session", nullptr },
- { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
- "don't read from config", nullptr },
- { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
- "don't detach from console", nullptr },
- { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
- nullptr, nullptr },
- { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
- "print messages to stderr", nullptr },
- { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
- "verbose logging", nullptr },
- { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version,
- "print version number", nullptr },
- { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }
- };
-
+ bool use_config_file = true;
options->kill = false;
options->daemon = true;
options->log_stderr = false;
options->verbose = false;
- context = g_option_context_new("[path/to/mpd.conf]");
- g_option_context_add_main_entries(context, entries, nullptr);
-
- g_option_context_set_summary(context, summary);
-
- GError *gerror = nullptr;
- ret = g_option_context_parse(context, &argc, &argv, &gerror);
- g_option_context_free(context);
-
- if (!ret)
- FatalError("option parsing failed", gerror);
+ // First pass: handle command line options
+ OptionParser parser(argc, argv);
+ while (parser.HasEntries()) {
+ if (!parser.ParseNext())
+ continue;
+ if (parser.CheckOption(opt_kill)) {
+ options->kill = true;
+ continue;
+ }
+ if (parser.CheckOption(opt_no_config)) {
+ use_config_file = false;
+ continue;
+ }
+ if (parser.CheckOption(opt_no_daemon)) {
+ options->daemon = false;
+ continue;
+ }
+ if (parser.CheckOption(opt_stderr, opt_stdout)) {
+ options->log_stderr = true;
+ continue;
+ }
+ if (parser.CheckOption(opt_verbose)) {
+ options->verbose = true;
+ continue;
+ }
+ if (parser.CheckOption(opt_version))
+ version();
+ if (parser.CheckOption(opt_help, opt_help_alt))
+ help();
- if (option_version)
- version();
+ error.Format(cmdline_domain, "invalid option: %s",
+ parser.GetOption());
+ return false;
+ }
/* initialize the logging library, so the configuration file
parser can use it already */
log_early_init(options->verbose);
- options->daemon = !option_no_daemon;
-
- if (option_no_config) {
+ if (!use_config_file) {
LogDebug(cmdline_domain,
"Ignoring config, using daemon defaults");
return true;
- } else if (argc <= 1) {
- /* default configuration file path */
+ }
-#ifdef WIN32
- AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()),
- CONFIG_FILE_LOCATION);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- const char *const*system_config_dirs =
- g_get_system_config_dirs();
-
- for (unsigned i = 0; system_config_dirs[i] != nullptr; ++i) {
- path = PathBuildChecked(AllocatedPath::FromUTF8(system_config_dirs[i]),
- CONFIG_FILE_LOCATION);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
+ // Second pass: find non-option parameters (i.e. config file)
+ const char *config_file = nullptr;
+ for (int i = 1; i < argc; ++i) {
+ if (OptionParser::IsOption(argv[i]))
+ continue;
+ if (config_file == nullptr) {
+ config_file = argv[i];
+ continue;
}
+ error.Set(cmdline_domain, "too many arguments");
+ return false;
+ }
+
+ if (config_file != nullptr) {
+ /* use specified configuration file */
+ return ReadConfigFile(Path::FromFS(config_file), error);
+ }
+
+ /* use default configuration file path */
+
+ ConfigLoader loader(error);
+
+ bool found =
+#ifdef WIN32
+ loader.TryFile(GetUserConfigDir(), CONFIG_FILE_LOCATION) ||
+ loader.TryFile(GetSystemConfigDir(), CONFIG_FILE_LOCATION) ||
+ loader.TryFile(GetAppBaseDir(), APP_CONFIG_FILE_LOCATION);
#else
- AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()),
- USER_CONFIG_FILE_LOCATION_XDG);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()),
- USER_CONFIG_FILE_LOCATION1);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()),
- USER_CONFIG_FILE_LOCATION2);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
-
- path = AllocatedPath::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION);
- if (!path.IsNull() && FileExists(path))
- return ReadConfigFile(path, error);
+ loader.TryFile(GetUserConfigDir(),
+ USER_CONFIG_FILE_LOCATION_XDG) ||
+ loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION1) ||
+ loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION2) ||
+ loader.TryFile(Path::FromFS(SYSTEM_CONFIG_FILE_LOCATION));
#endif
-
+ if (!found) {
error.Set(cmdline_domain, "No configuration file found");
return false;
- } else if (argc == 2) {
- /* specified configuration file */
- return ReadConfigFile(Path::FromFS(argv[1]), error);
- } else {
- error.Set(cmdline_domain, "too many arguments");
- return false;
}
+
+ return loader.GetResult();
}
diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx
index 214150eae..d8dedb1fb 100644
--- a/src/CommandLine.hxx
+++ b/src/CommandLine.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,15 +20,13 @@
#ifndef MPD_COMMAND_LINE_HXX
#define MPD_COMMAND_LINE_HXX
-#include <glib.h>
-
class Error;
struct options {
- gboolean kill;
- gboolean daemon;
- gboolean log_stderr;
- gboolean verbose;
+ bool kill;
+ bool daemon;
+ bool log_stderr;
+ bool verbose;
};
bool
diff --git a/src/Compiler.h b/src/Compiler.h
index b736b5ac7..fea971526 100644
--- a/src/Compiler.h
+++ b/src/Compiler.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx
deleted file mode 100644
index c6cabee6e..000000000
--- a/src/ConfigData.cxx
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ConfigData.hxx"
-#include "ConfigParser.hxx"
-#include "ConfigPath.hxx"
-#include "util/Error.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "system/FatalError.hxx"
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-int
-block_param::GetIntValue() const
-{
- char *endptr;
- long value2 = strtol(value.c_str(), &endptr, 0);
- if (*endptr != 0)
- FormatFatalError("Not a valid number in line %i", line);
-
- return value2;
-}
-
-unsigned
-block_param::GetUnsignedValue() const
-{
- char *endptr;
- unsigned long value2 = strtoul(value.c_str(), &endptr, 0);
- if (*endptr != 0)
- FormatFatalError("Not a valid number in line %i", line);
-
- return (unsigned)value2;
-}
-
-bool
-block_param::GetBoolValue() const
-{
- bool value2;
- if (!get_bool(value.c_str(), &value2))
- FormatFatalError("%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(_value), line(_line) {}
-
-config_param::~config_param()
-{
- delete next;
-}
-
-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();
-}
-
-AllocatedPath
-config_param::GetBlockPath(const char *name, const char *default_value,
- Error &error) const
-{
- assert(!error.IsDefined());
-
- int line2 = line;
- const char *s;
-
- const block_param *bp = GetBlockParam(name);
- if (bp != nullptr) {
- line2 = bp->line;
- s = bp->value.c_str();
- } else {
- if (default_value == nullptr)
- return AllocatedPath::Null();
-
- s = default_value;
- }
-
- AllocatedPath path = ParsePath(s, error);
- if (gcc_unlikely(path.IsNull()))
- error.FormatPrefix("Invalid path in \"%s\" at line %i: ",
- name, line2);
-
- return path;
-}
-
-AllocatedPath
-config_param::GetBlockPath(const char *name, Error &error) const
-{
- return GetBlockPath(name, nullptr, error);
-}
-
-int
-config_param::GetBlockValue(const char *name, int default_value) const
-{
- const block_param *bp = GetBlockParam(name);
- if (bp == nullptr)
- return default_value;
-
- return bp->GetIntValue();
-}
-
-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
deleted file mode 100644
index b41b27420..000000000
--- a/src/ConfigData.hxx
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_DATA_HXX
-#define MPD_CONFIG_DATA_HXX
-
-#include "ConfigOption.hxx"
-#include "Compiler.h"
-
-#include <string>
-#include <array>
-#include <vector>
-
-class AllocatedPath;
-class Error;
-
-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
- int GetIntValue() const;
-
- 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;
-
- std::string 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), 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;
-
- /**
- * Same as config_dup_path(), but looks up the setting in the
- * specified block.
- */
- AllocatedPath GetBlockPath(const char *name, const char *default_value,
- Error &error) const;
-
- AllocatedPath GetBlockPath(const char *name, Error &error) const;
-
- gcc_pure
- int GetBlockValue(const char *name, int default_value) 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/ConfigDefaults.hxx b/src/ConfigDefaults.hxx
deleted file mode 100644
index 9cfe61582..000000000
--- a/src/ConfigDefaults.hxx
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_DEFAULTS_HXX
-#define MPD_CONFIG_DEFAULTS_HXX
-
-static constexpr unsigned DEFAULT_PLAYLIST_MAX_LENGTH = 16 * 1024;
-static constexpr bool DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS = false;
-
-#endif
diff --git a/src/ConfigError.cxx b/src/ConfigError.cxx
deleted file mode 100644
index bd529e670..000000000
--- a/src/ConfigError.cxx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "ConfigError.hxx"
-#include "util/Domain.hxx"
-
-const Domain config_domain("config");
diff --git a/src/ConfigError.hxx b/src/ConfigError.hxx
deleted file mode 100644
index a8917d7d1..000000000
--- a/src/ConfigError.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_ERROR_HXX
-#define MPD_CONFIG_ERROR_HXX
-
-extern const class Domain config_domain;
-
-#endif
diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx
deleted file mode 100644
index 90859a89a..000000000
--- a/src/ConfigFile.cxx
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ConfigFile.hxx"
-#include "ConfigError.hxx"
-#include "ConfigData.hxx"
-#include "ConfigTemplates.hxx"
-#include "util/Tokenizer.hxx"
-#include "util/StringUtil.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "fs/Limits.hxx"
-#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-#include <errno.h>
-
-#define MAX_STRING_SIZE MPD_PATH_MAX+80
-
-#define CONF_COMMENT '#'
-
-static constexpr Domain config_file_domain("config_file");
-
-static bool
-config_read_name_value(struct config_param *param, char *input, unsigned line,
- Error &error)
-{
- Tokenizer tokenizer(input);
-
- const char *name = tokenizer.NextWord(error);
- if (name == nullptr) {
- assert(!tokenizer.IsEnd());
- return false;
- }
-
- const char *value = tokenizer.NextString(error);
- if (value == nullptr) {
- if (tokenizer.IsEnd()) {
- error.Set(config_file_domain, "Value missing");
- } else {
- assert(error.IsDefined());
- }
-
- return false;
- }
-
- if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) {
- error.Set(config_file_domain, "Unknown tokens after value");
- return false;
- }
-
- const struct block_param *bp = param->GetBlockParam(name);
- if (bp != nullptr) {
- error.Format(config_file_domain,
- "\"%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, Error &error)
-{
- struct config_param *ret = new config_param(*count);
-
- while (true) {
- char *line;
-
- line = fgets(string, MAX_STRING_SIZE, fp);
- if (line == nullptr) {
- delete ret;
- error.Set(config_file_domain,
- "Expected '}' before end-of-file");
- return nullptr;
- }
-
- (*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;
- error.Format(config_file_domain,
- "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;
- error.FormatPrefix("line %i: ", *count);
- return nullptr;
- }
- }
-}
-
-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, Error &error)
-{
- 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;
-
- 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 == nullptr) {
- assert(!tokenizer.IsEnd());
- error.FormatPrefix("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) {
- error.Format(config_file_domain,
- "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;
- error.Format(config_file_domain,
- "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() != '{') {
- error.Format(config_file_domain,
- "line %i: '{' expected", count);
- return false;
- }
-
- line = strchug_fast(tokenizer.Rest() + 1);
- if (*line != 0 && *line != CONF_COMMENT) {
- error.Format(config_file_domain,
- "line %i: Unknown tokens after '{'",
- count);
- return false;
- }
-
- param = config_read_block(fp, &count, string, error);
- if (param == nullptr) {
- return false;
- }
- } else {
- /* a string value */
-
- value = tokenizer.NextString(error);
- if (value == nullptr) {
- if (tokenizer.IsEnd())
- error.Format(config_file_domain,
- "line %i: Value missing",
- count);
- else
- error.FormatPrefix("line %i: ", count);
-
- return false;
- }
-
- if (!tokenizer.IsEnd() &&
- tokenizer.CurrentChar() != CONF_COMMENT) {
- error.Format(config_file_domain,
- "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, Path path, Error &error)
-{
- assert(!path.IsNull());
- const std::string path_utf8 = path.ToUTF8();
-
- FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
-
- FILE *fp = FOpen(path, FOpenMode::ReadText);
- if (fp == nullptr) {
- error.FormatErrno("Failed to open %s", path_utf8.c_str());
- return false;
- }
-
- bool result = ReadConfigFile(config_data, fp, error);
- fclose(fp);
- return result;
-}
diff --git a/src/ConfigFile.hxx b/src/ConfigFile.hxx
deleted file mode 100644
index 8c4ddbbd3..000000000
--- a/src/ConfigFile.hxx
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_FILE_HXX
-#define MPD_CONFIG_FILE_HXX
-
-class Error;
-class Path;
-struct ConfigData;
-
-bool
-ReadConfigFile(ConfigData &data, Path path, Error &error);
-
-#endif
diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx
deleted file mode 100644
index 49b9c08fb..000000000
--- a/src/ConfigGlobal.cxx
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ConfigGlobal.hxx"
-#include "ConfigParser.hxx"
-#include "ConfigData.hxx"
-#include "ConfigFile.hxx"
-#include "ConfigPath.hxx"
-#include "ConfigError.hxx"
-#include "fs/Path.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "util/Error.hxx"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-static ConfigData config_data;
-
-void config_global_finish(void)
-{
- for (auto i : config_data.params)
- delete i;
-}
-
-void config_global_init(void)
-{
-}
-
-bool
-ReadConfigFile(Path path, Error &error)
-{
- return ReadConfigFile(config_data, path, error);
-}
-
-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)
- FormatWarning(config_domain,
- "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 == nullptr)
- return default_value;
-
- return param->value.c_str();
-}
-
-AllocatedPath
-config_get_path(ConfigOption option, Error &error)
-{
- const struct config_param *param = config_get_param(option);
- if (param == nullptr)
- return AllocatedPath::Null();
-
- return config_parse_path(param, error);
-}
-
-AllocatedPath
-config_parse_path(const struct config_param *param, Error & error)
-{
- AllocatedPath path = ParsePath(param->value.c_str(), error);
- if (gcc_unlikely(path.IsNull()))
- error.FormatPrefix("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 == nullptr)
- return default_value;
-
- value = strtol(param->value.c_str(), &endptr, 0);
- if (*endptr != 0 || value < 0)
- FormatFatalError("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 == nullptr)
- return default_value;
-
- value = strtol(param->value.c_str(), &endptr, 0);
- if (*endptr != 0)
- FormatFatalError("Not a valid number in line %i", param->line);
-
- if (value <= 0)
- FormatFatalError("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 == nullptr)
- return default_value;
-
- success = get_bool(param->value.c_str(), &value);
- if (!success)
- FormatFatalError("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
deleted file mode 100644
index 978db3145..000000000
--- a/src/ConfigGlobal.hxx
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_GLOBAL_HXX
-#define MPD_CONFIG_GLOBAL_HXX
-
-#include "ConfigOption.hxx"
-#include "Compiler.h"
-
-class Error;
-class Path;
-class AllocatedPath;
-
-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
-ReadConfigFile(Path path, Error &error);
-
-/* don't free the returned value
- set _last_ to nullptr 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, nullptr);
-}
-
-/* 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 AllocatedPath::Null() if the value is not present. If the path
- * could not be parsed, returns AllocatedPath::Null() and sets the error.
- */
-AllocatedPath
-config_get_path(enum ConfigOption option, Error &error);
-
-/**
- * Parse a configuration parameter as a path.
- * If there is a tilde prefix, it is expanded. If the path could
- * not be parsed, returns AllocatedPath::Null() and sets the error.
- */
-AllocatedPath
-config_parse_path(const struct config_param *param, Error & 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);
-
-#endif
diff --git a/src/ConfigOption.hxx b/src/ConfigOption.hxx
deleted file mode 100644
index e8a2a86c8..000000000
--- a/src/ConfigOption.hxx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_OPTION_HXX
-#define MPD_CONFIG_OPTION_HXX
-
-#include "Compiler.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
deleted file mode 100644
index 73381d8a0..000000000
--- a/src/ConfigParser.cxx
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "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
deleted file mode 100644
index 00fd42fe3..000000000
--- a/src/ConfigParser.hxx
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index b88de3934..000000000
--- a/src/ConfigPath.cxx
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ConfigPath.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/Traits.hxx"
-#include "fs/Domain.hxx"
-#include "util/Error.hxx"
-#include "ConfigGlobal.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#ifndef WIN32
-#include <pwd.h>
-
-/**
- * Determine a given user's home directory.
- */
-static AllocatedPath
-GetHome(const char *user, Error &error)
-{
- passwd *pw = getpwnam(user);
- if (pw == nullptr) {
- error.Format(path_domain,
- "no such user: %s", user);
- return AllocatedPath::Null();
- }
-
- return AllocatedPath::FromFS(pw->pw_dir);
-}
-
-/**
- * Determine the current user's home directory.
- */
-static AllocatedPath
-GetHome(Error &error)
-{
- const char *home = g_get_home_dir();
- if (home == nullptr) {
- error.Set(path_domain,
- "problems getting home for current user");
- return AllocatedPath::Null();
- }
-
- return AllocatedPath::FromUTF8(home, error);
-}
-
-/**
- * Determine the configured user's home directory.
- */
-static AllocatedPath
-GetConfiguredHome(Error &error)
-{
- const char *user = config_get_string(CONF_USER, nullptr);
- return user != nullptr
- ? GetHome(user, error)
- : GetHome(error);
-}
-
-#endif
-
-AllocatedPath
-ParsePath(const char *path, Error &error)
-{
- assert(path != nullptr);
-
-#ifndef WIN32
- if (path[0] == '~') {
- ++path;
-
- if (*path == '\0')
- return GetConfiguredHome(error);
-
- AllocatedPath home = AllocatedPath::Null();
-
- if (*path == '/') {
- home = GetConfiguredHome(error);
-
- ++path;
- } else {
- const char *slash = strchr(path, '/');
- const char *end = slash == nullptr
- ? path + strlen(path)
- : slash;
- const std::string user(path, end);
- home = GetHome(user.c_str(), error);
-
- if (slash == nullptr)
- return home;
-
- path = slash + 1;
- }
-
- if (home.IsNull())
- return AllocatedPath::Null();
-
- AllocatedPath path2 = AllocatedPath::FromUTF8(path, error);
- if (path2.IsNull())
- return AllocatedPath::Null();
-
- return AllocatedPath::Build(home, path2);
- } else if (!PathTraits::IsAbsoluteUTF8(path)) {
- error.Format(path_domain,
- "not an absolute path: %s", path);
- return AllocatedPath::Null();
- } else {
-#endif
- return AllocatedPath::FromUTF8(path, error);
-#ifndef WIN32
- }
-#endif
-}
diff --git a/src/ConfigPath.hxx b/src/ConfigPath.hxx
deleted file mode 100644
index 99abd85b7..000000000
--- a/src/ConfigPath.hxx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CONFIG_PATH_HXX
-#define MPD_CONFIG_PATH_HXX
-
-class AllocatedPath;
-class Error;
-
-AllocatedPath
-ParsePath(const char *path, Error &error);
-
-#endif
diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx
deleted file mode 100644
index 6c6bf1689..000000000
--- a/src/ConfigTemplates.cxx
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "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
deleted file mode 100644
index 4f5460460..000000000
--- a/src/ConfigTemplates.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
index 601d74dc2..e3cc95b0d 100644
--- a/src/CrossFade.cxx
+++ b/src/CrossFade.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "config.h"
#include "CrossFade.hxx"
+#include "Chrono.hxx"
#include "MusicChunk.hxx"
#include "AudioFormat.hxx"
#include "util/NumberParser.hxx"
@@ -26,8 +27,6 @@
#include "Log.hxx"
#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
static constexpr Domain cross_fade_domain("cross_fade");
@@ -87,7 +86,7 @@ mixramp_interpolate(const char *ramp_list, float required_db)
}
unsigned
-CrossFadeSettings::Calculate(float total_time,
+CrossFadeSettings::Calculate(SignedSongTime total_time,
float replay_gain_db, float replay_gain_prev_db,
const char *mixramp_start, const char *mixramp_prev_end,
const AudioFormat af,
@@ -97,7 +96,8 @@ CrossFadeSettings::Calculate(float total_time,
unsigned int chunks = 0;
float chunks_f;
- if (duration < 0 || duration >= total_time ||
+ if (total_time.IsNegative() ||
+ duration < 0 || duration >= total_time.ToDoubleS() ||
/* we can't crossfade when the audio formats are different */
af != old_format)
return 0;
diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx
index c47db84e1..81e96e8d3 100644
--- a/src/CrossFade.hxx
+++ b/src/CrossFade.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include "Compiler.h"
struct AudioFormat;
+class SignedSongTime;
struct CrossFadeSettings {
/**
@@ -60,7 +61,7 @@ struct CrossFadeSettings {
* should be disabled for this song change
*/
gcc_pure
- unsigned Calculate(float total_time,
+ unsigned Calculate(SignedSongTime total_time,
float replay_gain_db, float replay_gain_prev_db,
const char *mixramp_start,
const char *mixramp_prev_end,
diff --git a/src/Daemon.cxx b/src/Daemon.cxx
deleted file mode 100644
index 557c47777..000000000
--- a/src/Daemon.cxx
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Daemon.hxx"
-#include "system/FatalError.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#ifndef WIN32
-#include <signal.h>
-#include <pwd.h>
-#include <grp.h>
-#endif
-
-static constexpr Domain daemon_domain("daemon");
-
-#ifndef WIN32
-
-/** the Unix user name which MPD runs as */
-static char *user_name;
-
-/** the Unix user id which MPD runs as */
-static uid_t user_uid = (uid_t)-1;
-
-/** the Unix group id which MPD runs as */
-static gid_t user_gid = (gid_t)-1;
-
-/** the absolute path of the pidfile */
-static AllocatedPath pidfile = AllocatedPath::Null();
-
-/* whether "group" conf. option was given */
-static bool had_group = false;
-
-
-void
-daemonize_kill(void)
-{
- FILE *fp;
- int pid, ret;
-
- if (pidfile.IsNull())
- FatalError("no pid_file specified in the config file");
-
- fp = FOpen(pidfile, "r");
- if (fp == nullptr) {
- const std::string utf8 = pidfile.ToUTF8();
- FormatFatalSystemError("Unable to open pid file \"%s\"",
- utf8.c_str());
- }
-
- if (fscanf(fp, "%i", &pid) != 1) {
- const std::string utf8 = pidfile.ToUTF8();
- FormatFatalError("unable to read the pid from file \"%s\"",
- utf8.c_str());
- }
- fclose(fp);
-
- ret = kill(pid, SIGTERM);
- if (ret < 0)
- FormatFatalSystemError("unable to kill process %i",
- int(pid));
-
- exit(EXIT_SUCCESS);
-}
-
-void
-daemonize_close_stdin(void)
-{
- close(STDIN_FILENO);
- open("/dev/null", O_RDONLY);
-}
-
-void
-daemonize_set_user(void)
-{
- if (user_name == nullptr)
- return;
-
- /* set gid */
- if (user_gid != (gid_t)-1 && user_gid != getgid() &&
- setgid(user_gid) == -1) {
- FormatFatalSystemError("Failed to set group %d",
- (int)user_gid);
- }
-
-#ifdef _BSD_SOURCE
- /* init supplementary groups
- * (must be done before we change our uid)
- */
- if (!had_group &&
- /* no need to set the new user's supplementary groups if
- we are already this user */
- user_uid != getuid() &&
- initgroups(user_name, user_gid) == -1) {
- FormatFatalSystemError("Failed to set supplementary groups "
- "of user \"%s\"",
- user_name);
- }
-#endif
-
- /* set uid */
- if (user_uid != (uid_t)-1 && user_uid != getuid() &&
- setuid(user_uid) == -1) {
- FormatFatalSystemError("Failed to set user \"%s\"",
- user_name);
- }
-}
-
-static void
-daemonize_detach(void)
-{
- /* flush all file handles before duplicating the buffers */
-
- fflush(nullptr);
-
-#ifdef HAVE_DAEMON
-
- if (daemon(0, 1))
- FatalSystemError("daemon() failed");
-
-#elif defined(HAVE_FORK)
-
- /* detach from parent process */
-
- switch (fork()) {
- case -1:
- FatalSystemError("fork() failed");
- case 0:
- break;
- default:
- /* exit the parent process */
- _exit(EXIT_SUCCESS);
- }
-
- /* release the current working directory */
-
- if (chdir("/") < 0)
- FatalError("problems changing to root directory");
-
- /* detach from the current session */
-
- setsid();
-
-#else
- FatalError("no support for daemonizing");
-#endif
-
- LogDebug(daemon_domain, "daemonized");
-}
-
-void
-daemonize(bool detach)
-{
- FILE *fp = nullptr;
-
- if (!pidfile.IsNull()) {
- /* do this before daemon'izing so we can fail gracefully if we can't
- * write to the pid file */
- LogDebug(daemon_domain, "opening pid file");
- fp = FOpen(pidfile, "w+");
- if (!fp) {
- const std::string utf8 = pidfile.ToUTF8();
- FormatFatalSystemError("Failed to create pid file \"%s\"",
- pidfile.c_str());
- }
- }
-
- if (detach)
- daemonize_detach();
-
- if (!pidfile.IsNull()) {
- LogDebug(daemon_domain, "writing pid file");
- fprintf(fp, "%lu\n", (unsigned long)getpid());
- fclose(fp);
- }
-}
-
-void
-daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile)
-{
- if (user) {
- struct passwd *pwd = getpwnam(user);
- if (pwd == nullptr)
- FormatFatalError("no such user \"%s\"", user);
-
- user_uid = pwd->pw_uid;
- user_gid = pwd->pw_gid;
-
- user_name = g_strdup(user);
-
- /* this is needed by libs such as arts */
- g_setenv("HOME", pwd->pw_dir, true);
- }
-
- if (group) {
- struct group *grp = getgrnam(group);
- if (grp == nullptr)
- FormatFatalError("no such group \"%s\"", group);
- user_gid = grp->gr_gid;
- had_group = true;
- }
-
-
- pidfile = std::move(_pidfile);
-}
-
-void
-daemonize_finish(void)
-{
- if (!pidfile.IsNull()) {
- RemoveFile(pidfile);
- pidfile = AllocatedPath::Null();
- }
-
- g_free(user_name);
-}
-
-#endif
diff --git a/src/Daemon.hxx b/src/Daemon.hxx
deleted file mode 100644
index ecd090d5b..000000000
--- a/src/Daemon.hxx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DAEMON_HXX
-#define MPD_DAEMON_HXX
-
-class AllocatedPath;
-
-#ifndef WIN32
-void
-daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile);
-#else
-static inline void
-daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile)
-{ (void)user; (void)group; (void)pidfile; }
-#endif
-
-#ifndef WIN32
-void
-daemonize_finish(void);
-#else
-static inline void
-daemonize_finish(void)
-{ /* nop */ }
-#endif
-
-/**
- * Kill the MPD which is currently running, pid determined from the
- * pid file.
- */
-#ifndef WIN32
-void
-daemonize_kill(void);
-#else
-#include "system/FatalError.hxx"
-static inline void
-daemonize_kill(void)
-{
- FatalError("--kill is not available on WIN32");
-}
-#endif
-
-/**
- * Close stdin (fd 0) and re-open it as /dev/null.
- */
-#ifndef WIN32
-void
-daemonize_close_stdin(void);
-#else
-static inline void
-daemonize_close_stdin(void) {}
-#endif
-
-/**
- * Change to the configured Unix user.
- */
-#ifndef WIN32
-void
-daemonize_set_user(void);
-#else
-static inline void
-daemonize_set_user(void)
-{ /* nop */ }
-#endif
-
-#ifndef WIN32
-void
-daemonize(bool detach);
-#else
-static inline void
-daemonize(bool detach)
-{ (void)detach; }
-#endif
-
-#endif
diff --git a/src/DatabaseError.cxx b/src/DatabaseError.cxx
deleted file mode 100644
index 800442b9f..000000000
--- a/src/DatabaseError.cxx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DatabaseError.hxx"
-#include "util/Domain.hxx"
-
-const Domain db_domain("db");
diff --git a/src/DatabaseError.hxx b/src/DatabaseError.hxx
deleted file mode 100644
index c133ee357..000000000
--- a/src/DatabaseError.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DB_ERROR_HXX
-#define MPD_DB_ERROR_HXX
-
-class Domain;
-
-enum db_error {
- /**
- * The database is disabled, i.e. none is configured in this
- * MPD instance.
- */
- DB_DISABLED,
-
- DB_NOT_FOUND,
-};
-
-extern const Domain db_domain;
-
-#endif
diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx
deleted file mode 100644
index 50deaf48e..000000000
--- a/src/DatabaseGlue.cxx
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DatabaseGlue.hxx"
-#include "DatabaseSimple.hxx"
-#include "DatabaseRegistry.hxx"
-#include "DatabaseSave.hxx"
-#include "DatabaseError.hxx"
-#include "Directory.hxx"
-#include "util/Error.hxx"
-#include "ConfigData.hxx"
-#include "Stats.hxx"
-#include "DatabasePlugin.hxx"
-#include "db/SimpleDatabasePlugin.hxx"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-
-
-static Database *db;
-static bool db_is_open;
-static bool is_simple;
-
-bool
-DatabaseGlobalInit(const config_param &param, Error &error)
-{
- assert(db == nullptr);
- 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 == nullptr) {
- error.Format(db_domain,
- "No such database plugin: %s", plugin_name);
- return false;
- }
-
- db = plugin->create(param, error);
- return db != nullptr;
-}
-
-void
-DatabaseGlobalDeinit(void)
-{
- if (db_is_open)
- db->Close();
-
- if (db != nullptr)
- delete db;
-}
-
-const Database *
-GetDatabase()
-{
- assert(db == nullptr || db_is_open);
-
- return db;
-}
-
-const Database *
-GetDatabase(Error &error)
-{
- assert(db == nullptr || db_is_open);
-
- if (db == nullptr)
- error.Set(db_domain, DB_DISABLED, "No database");
-
- return db;
-}
-
-bool
-db_is_simple(void)
-{
- assert(db == nullptr || db_is_open);
-
- return is_simple;
-}
-
-Directory *
-db_get_root(void)
-{
- assert(db != nullptr);
- assert(db_is_simple());
-
- return ((SimpleDatabase *)db)->GetRoot();
-}
-
-Directory *
-db_get_directory(const char *name)
-{
-#if !CLANG_CHECK_VERSION(3,6)
- /* disabled on clang due to -Wtautological-pointer-compare */
- assert(name != nullptr);
-#endif
-
- if (db == nullptr)
- return nullptr;
-
- Directory *music_root = db_get_root();
- return music_root->LookupDirectory(name);
-}
-
-bool
-db_save(Error &error)
-{
- assert(db != nullptr);
- assert(db_is_open);
- assert(db_is_simple());
-
- return ((SimpleDatabase *)db)->Save(error);
-}
-
-bool
-DatabaseGlobalOpen(Error &error)
-{
- assert(db != nullptr);
- assert(!db_is_open);
-
- if (!db->Open(error))
- return false;
-
- db_is_open = true;
-
- stats_update();
-
- return true;
-}
-
-bool
-db_exists()
-{
- assert(db != nullptr);
- assert(db_is_open);
- assert(db_is_simple());
-
- return ((SimpleDatabase *)db)->GetUpdateStamp() > 0;
-}
diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx
deleted file mode 100644
index bc05942e0..000000000
--- a/src/DatabaseGlue.hxx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_GLUE_HXX
-#define MPD_DATABASE_GLUE_HXX
-
-#include "Compiler.h"
-
-struct config_param;
-class Database;
-class Error;
-
-/**
- * Initialize the database library.
- *
- * @param param the database configuration block
- */
-bool
-DatabaseGlobalInit(const config_param &param, Error &error);
-
-void
-DatabaseGlobalDeinit(void);
-
-bool
-DatabaseGlobalOpen(Error &error);
-
-/**
- * Returns the global #Database instance. May return nullptr if this MPD
- * configuration has no database (no music_directory was configured).
- */
-gcc_pure
-const Database *
-GetDatabase();
-
-/**
- * Returns the global #Database instance. May return nullptr if this MPD
- * configuration has no database (no music_directory was configured).
- */
-gcc_pure
-const Database *
-GetDatabase(Error &error);
-
-#endif
diff --git a/src/DatabaseHelpers.cxx b/src/DatabaseHelpers.cxx
deleted file mode 100644
index 4202b1247..000000000
--- a/src/DatabaseHelpers.cxx
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "DatabaseHelpers.hxx"
-#include "DatabasePlugin.hxx"
-#include "Song.hxx"
-#include "tag/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, TagType 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,
- TagType tag_type,
- VisitString visit_string,
- Error &error)
-{
- StringSet set;
-
- using namespace std::placeholders;
- const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1);
- if (!db.Visit(selection, f, error))
- return false;
-
- for (auto value : set)
- if (!visit_string(value, error))
- 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, Error &error)
-{
- 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))
- return false;
-
- stats.artist_count = artists.size();
- stats.album_count = albums.size();
- return true;
-}
diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx
deleted file mode 100644
index d8806bc69..000000000
--- a/src/DatabaseHelpers.hxx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_MEMORY_DATABASE_PLUGIN_HXX
-#define MPD_MEMORY_DATABASE_PLUGIN_HXX
-
-#include "DatabaseVisitor.hxx"
-#include "tag/TagType.h"
-#include "Compiler.h"
-
-class Error;
-class Database;
-struct DatabaseSelection;
-struct DatabaseStats;
-
-bool
-VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error);
-
-bool
-GetStats(const Database &db, const DatabaseSelection &selection,
- DatabaseStats &stats, Error &error);
-
-#endif
diff --git a/src/DatabaseLock.cxx b/src/DatabaseLock.cxx
deleted file mode 100644
index d85f72d3b..000000000
--- a/src/DatabaseLock.cxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DatabaseLock.hxx"
-#include "Compiler.h"
-
-Mutex db_mutex;
-
-#ifndef NDEBUG
-ThreadId db_mutex_holder;
-#endif
diff --git a/src/DatabaseLock.hxx b/src/DatabaseLock.hxx
deleted file mode 100644
index 1bd5cbe61..000000000
--- a/src/DatabaseLock.hxx
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 "Compiler.h"
-
-#include <assert.h>
-
-extern Mutex db_mutex;
-
-#ifndef NDEBUG
-
-#include "thread/Id.hxx"
-
-extern ThreadId db_mutex_holder;
-
-/**
- * Does the current thread hold the database lock?
- */
-gcc_pure
-static inline bool
-holding_db_lock(void)
-{
- return db_mutex_holder.IsInside();
-}
-
-#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.IsNull());
-#ifndef NDEBUG
- db_mutex_holder = ThreadId::GetCurrent();
-#endif
-}
-
-/**
- * Release the global database lock.
- */
-static inline void
-db_unlock(void)
-{
- assert(holding_db_lock());
-#ifndef NDEBUG
- db_mutex_holder = ThreadId::Null();
-#endif
-
- db_mutex.unlock();
-}
-
-class ScopeDatabaseLock {
-public:
- ScopeDatabaseLock() {
- db_lock();
- }
-
- ~ScopeDatabaseLock() {
- db_unlock();
- }
-};
-
-#endif
diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx
deleted file mode 100644
index b0cb19589..000000000
--- a/src/DatabasePlaylist.cxx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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, Error &error)
-{
- return spl_append_song(playlist_path_utf8, song, error);
-}
-
-bool
-search_add_to_playlist(const char *uri, const char *playlist_path_utf8,
- const SongFilter *filter,
- Error &error)
-{
- const Database *db = GetDatabase(error);
- 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);
-}
diff --git a/src/DatabasePlaylist.hxx b/src/DatabasePlaylist.hxx
deleted file mode 100644
index d5f1648d5..000000000
--- a/src/DatabasePlaylist.hxx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_PLAYLIST_HXX
-#define MPD_DATABASE_PLAYLIST_HXX
-
-#include "Compiler.h"
-
-class SongFilter;
-class Error;
-
-gcc_nonnull(1,2)
-bool
-search_add_to_playlist(const char *uri, const char *path_utf8,
- const SongFilter *filter,
- Error &error);
-
-#endif
diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx
deleted file mode 100644
index ccf899389..000000000
--- a/src/DatabasePlugin.hxx
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 "tag/TagType.h"
-#include "Compiler.h"
-
-#include <time.h>
-
-struct config_param;
-struct DatabaseSelection;
-struct db_visitor;
-struct Song;
-class Error;
-
-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 Error &error) {
- 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,
- Error &error) 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,
- Error &error) const = 0;
-
- bool Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- Error &error) const {
- return Visit(selection, visit_directory, visit_song,
- VisitPlaylist(), error);
- }
-
- bool Visit(const DatabaseSelection &selection, VisitSong visit_song,
- Error &error) const {
- return Visit(selection, VisitDirectory(), visit_song, error);
- }
-
- /**
- * Visit all unique tag values.
- */
- virtual bool VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const = 0;
-
- virtual bool GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats,
- Error &error) const = 0;
-
- /**
- * Returns the time stamp of the last database update.
- * Returns 0 if that is not not known/available.
- */
- gcc_pure
- virtual time_t GetUpdateStamp() const = 0;
-};
-
-struct DatabasePlugin {
- const char *name;
-
- /**
- * Allocates and configures a database.
- */
- Database *(*create)(const config_param &param,
- Error &error);
-};
-
-#endif
diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx
deleted file mode 100644
index 3732e98f3..000000000
--- a/src/DatabasePrint.cxx
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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/Tag.hxx"
-#include "Song.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabasePlugin.hxx"
-
-#include <functional>
-
-static bool
-PrintDirectoryBrief(Client &client, const Directory &directory)
-{
- if (!directory.IsRoot())
- client_printf(client, "directory: %s\n", directory.GetPath());
-
- return true;
-}
-
-static bool
-PrintDirectoryFull(Client &client, const Directory &directory)
-{
- if (!directory.IsRoot()) {
- client_printf(client, "directory: %s\n", directory.GetPath());
- time_print(client, "Last-Modified", directory.mtime);
- }
-
- 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, const Song &song)
-{
- assert(song.parent != nullptr);
-
- song_print_uri(client, song);
-
- if (song.tag != nullptr && song.tag->has_playlist)
- /* this song file has an embedded CUE sheet */
- print_playlist_in_directory(client, *song.parent, song.uri);
-
- return true;
-}
-
-static bool
-PrintSongFull(Client &client, const Song &song)
-{
- assert(song.parent != nullptr);
-
- song_print_info(client, song);
-
- if (song.tag != nullptr && song.tag->has_playlist)
- /* this song file has an embedded CUE sheet */
- print_playlist_in_directory(client, *song.parent, song.uri);
-
- 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, Error &error)
-{
- const Database *db = GetDatabase(error);
- if (db == nullptr)
- return false;
-
- using namespace std::placeholders;
- const auto d = selection.filter == nullptr
- ? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief,
- std::ref(client), _1)
- : VisitDirectory();
- const auto s = std::bind(full ? PrintSongFull : PrintSongBrief,
- std::ref(client), _1);
- const auto p = selection.filter == nullptr
- ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief,
- std::ref(client), _1, _2)
- : VisitPlaylist();
-
- return db->Visit(selection, d, s, p, error);
-}
-
-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,
- Error &error)
-{
- const Database *db = GetDatabase(error);
- 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))
- return false;
-
- printSearchStats(client, &stats);
- return true;
-}
-
-bool
-printAllIn(Client &client, const char *uri_utf8, Error &error)
-{
- const DatabaseSelection selection(uri_utf8, true);
- return db_selection_print(client, selection, false, error);
-}
-
-bool
-printInfoForAllIn(Client &client, const char *uri_utf8,
- Error &error)
-{
- const DatabaseSelection selection(uri_utf8, true);
- return db_selection_print(client, selection, true, error);
-}
-
-static bool
-PrintSongURIVisitor(Client &client, Song &song)
-{
- song_print_uri(client, song);
-
- return true;
-}
-
-static bool
-PrintUniqueTag(Client &client, TagType 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,
- Error &error)
-{
- const Database *db = GetDatabase(error);
- 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,
- std::ref(client), _1);
- return db->Visit(selection, f, error);
- } else {
- using namespace std::placeholders;
- const auto f = std::bind(PrintUniqueTag, std::ref(client),
- (TagType)type, _1);
- return db->VisitUniqueTags(selection, (TagType)type,
- f, error);
- }
-}
diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx
deleted file mode 100644
index 36a68d87b..000000000
--- a/src/DatabasePrint.hxx
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DB_PRINT_H
-#define MPD_DB_PRINT_H
-
-#include "Compiler.h"
-
-class SongFilter;
-struct DatabaseSelection;
-struct db_visitor;
-class Client;
-class Error;
-
-bool
-db_selection_print(Client &client, const DatabaseSelection &selection,
- bool full, Error &error);
-
-gcc_nonnull(2)
-bool
-printAllIn(Client &client, const char *uri_utf8, Error &error);
-
-gcc_nonnull(2)
-bool
-printInfoForAllIn(Client &client, const char *uri_utf8,
- Error &error);
-
-gcc_nonnull(2)
-bool
-searchStatsForSongsIn(Client &client, const char *name,
- const SongFilter *filter,
- Error &error);
-
-bool
-listAllUniqueTags(Client &client, int type,
- const SongFilter *filter,
- Error &error);
-
-#endif
diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx
deleted file mode 100644
index 98f80c5b6..000000000
--- a/src/DatabaseQueue.cxx
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DatabaseQueue.hxx"
-#include "DatabaseSelection.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabasePlugin.hxx"
-#include "Partition.hxx"
-#include "util/Error.hxx"
-
-#include <functional>
-
-static bool
-AddToQueue(Partition &partition, Song &song, Error &error)
-{
- PlaylistResult result =
- partition.playlist.AppendSong(partition.pc, &song, nullptr);
- if (result != PlaylistResult::SUCCESS) {
- error.Set(playlist_domain, int(result), "Playlist error");
- return false;
- }
-
- return true;
-}
-
-bool
-AddFromDatabase(Partition &partition, const DatabaseSelection &selection,
- Error &error)
-{
- const Database *db = GetDatabase(error);
- 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);
-}
diff --git a/src/DatabaseQueue.hxx b/src/DatabaseQueue.hxx
deleted file mode 100644
index 4faa708c2..000000000
--- a/src/DatabaseQueue.hxx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_QUEUE_HXX
-#define MPD_DATABASE_QUEUE_HXX
-
-struct Partition;
-struct DatabaseSelection;
-class Error;
-
-bool
-AddFromDatabase(Partition &partition, const DatabaseSelection &selection,
- Error &error);
-
-#endif
diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx
deleted file mode 100644
index 4ce4a62cb..000000000
--- a/src/DatabaseRegistry.cxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
- nullptr
-};
-
-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
deleted file mode 100644
index d8c5dff12..000000000
--- a/src/DatabaseRegistry.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_REGISTRY_HXX
-#define MPD_DATABASE_REGISTRY_HXX
-
-#include "Compiler.h"
-
-struct DatabasePlugin;
-
-/**
- * nullptr 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
deleted file mode 100644
index abfd4a34f..000000000
--- a/src/DatabaseSave.cxx
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DatabaseSave.hxx"
-#include "DatabaseLock.hxx"
-#include "DatabaseError.hxx"
-#include "Directory.hxx"
-#include "DirectorySave.hxx"
-#include "Song.hxx"
-#include "TextFile.hxx"
-#include "tag/Tag.hxx"
-#include "tag/TagSettings.h"
-#include "fs/Charset.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-#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: "
-
-static constexpr unsigned DB_FORMAT = 1;
-
-void
-db_save_internal(FILE *fp, const Directory &music_root)
-{
- 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, GetFSCharset());
-
- 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, Error &error)
-{
- char *line;
- unsigned format = 0;
- bool found_charset = false, found_version = false;
- bool success;
- bool tags[TAG_NUM_OF_ITEM_TYPES];
-
- /* get initial info */
- line = file.ReadLine();
- if (line == nullptr || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
- error.Set(db_domain, "Database corrupted");
- return false;
- }
-
- memset(tags, false, sizeof(tags));
-
- while ((line = file.ReadLine()) != nullptr &&
- 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) {
- error.Set(db_domain, "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) {
- error.Set(db_domain, "Duplicate charset line");
- return false;
- }
-
- found_charset = true;
-
- new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
- const char *const old_charset = GetFSCharset();
- if (*old_charset != 0
- && strcmp(new_charset, old_charset) != 0) {
- error.Format(db_domain,
- "Existing database has charset "
- "\"%s\" instead of \"%s\"; "
- "discarding database file",
- new_charset, old_charset);
- return false;
- }
- } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
- const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
- TagType tag = tag_name_parse(name);
- if (tag == TAG_NUM_OF_ITEM_TYPES) {
- error.Format(db_domain,
- "Unrecognized tag '%s', "
- "discarding database file",
- name);
- return false;
- }
-
- tags[tag] = true;
- } else {
- error.Format(db_domain, "Malformed line: %s", line);
- return false;
- }
- }
-
- if (format != DB_FORMAT) {
- error.Set(db_domain,
- "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]) {
- error.Set(db_domain,
- "Tag list mismatch, "
- "discarding database file");
- return false;
- }
- }
-
- LogDebug(db_domain, "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
deleted file mode 100644
index 741189973..000000000
--- a/src/DatabaseSave.hxx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_SAVE_HXX
-#define MPD_DATABASE_SAVE_HXX
-
-#include <stdio.h>
-
-struct Directory;
-class TextFile;
-class Error;
-
-void
-db_save_internal(FILE *file, const Directory &root);
-
-bool
-db_load_internal(TextFile &file, Directory &root, Error &error);
-
-#endif
diff --git a/src/DatabaseSelection.cxx b/src/DatabaseSelection.cxx
deleted file mode 100644
index ffe5f7d39..000000000
--- a/src/DatabaseSelection.cxx
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "DatabaseSelection.hxx"
-#include "SongFilter.hxx"
-
-DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
- const SongFilter *_filter)
- :uri(_uri), recursive(_recursive), filter(_filter)
-{
- /* optimization: if the caller didn't specify a base URI, pick
- the one from SongFilter */
- if (uri.empty() && filter != nullptr)
- uri = filter->GetBase();
-}
-
-bool
-DatabaseSelection::IsEmpty() const
-{
- return uri.empty() && (filter == nullptr || filter->IsEmpty());
-}
-
-bool
-DatabaseSelection::HasOtherThanBase() const
-{
- return filter != nullptr && filter->HasOtherThanBase();
-}
-
-bool
-DatabaseSelection::Match(const Song &song) const
-{
- return filter == nullptr || filter->Match(song);
-}
diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx
deleted file mode 100644
index 4825c738c..000000000
--- a/src/DatabaseSelection.hxx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_SELECTION_HXX
-#define MPD_DATABASE_SELECTION_HXX
-
-#include "Compiler.h"
-
-#include <string>
-
-class SongFilter;
-struct Song;
-
-struct DatabaseSelection {
- /**
- * The base URI of the search (UTF-8). Must not begin or end
- * with a slash. An empty string searches the whole database.
- */
- std::string uri;
-
- /**
- * Recursively search all sub directories?
- */
- bool recursive;
-
- const SongFilter *filter;
-
- DatabaseSelection(const char *_uri, bool _recursive,
- const SongFilter *_filter=nullptr);
-
- gcc_pure
- bool IsEmpty() const;
-
- /**
- * Does this selection contain constraints other than "base"?
- */
- gcc_pure
- bool HasOtherThanBase() const;
-
- gcc_pure
- bool Match(const Song &song) const;
-};
-
-#endif
diff --git a/src/DatabaseSimple.hxx b/src/DatabaseSimple.hxx
deleted file mode 100644
index 6d52ac0b3..000000000
--- a/src/DatabaseSimple.hxx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_SIMPLE_HXX
-#define MPD_DATABASE_SIMPLE_HXX
-
-#include "Compiler.h"
-
-#include <sys/time.h>
-
-struct config_param;
-struct Directory;
-struct db_selection;
-struct db_visitor;
-class Error;
-
-/**
- * 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(Error &error);
-
-/**
- * Returns true if there is a valid database file on the disk.
- *
- * May only be used if db_is_simple() returns true.
- */
-gcc_pure
-bool
-db_exists();
-
-#endif
diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx
deleted file mode 100644
index 256b08442..000000000
--- a/src/DatabaseVisitor.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DATABASE_VISITOR_HXX
-#define MPD_DATABASE_VISITOR_HXX
-
-#include <functional>
-
-struct Directory;
-struct Song;
-struct PlaylistInfo;
-class Error;
-
-typedef std::function<bool(const Directory &, Error &)> VisitDirectory;
-typedef std::function<bool(struct Song &, Error &)> VisitSong;
-typedef std::function<bool(const PlaylistInfo &, const Directory &,
- Error &)> VisitPlaylist;
-
-typedef std::function<bool(const char *, Error &)> VisitString;
-
-#endif
diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx
deleted file mode 100644
index 334d069bf..000000000
--- a/src/DecoderAPI.cxx
+++ /dev/null
@@ -1,585 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderAPI.hxx"
-#include "DecoderError.hxx"
-#include "AudioConfig.hxx"
-#include "ReplayGainConfig.hxx"
-#include "MusicChunk.hxx"
-#include "MusicBuffer.hxx"
-#include "MusicPipe.hxx"
-#include "DecoderControl.hxx"
-#include "DecoderInternal.hxx"
-#include "Song.hxx"
-#include "InputStream.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-
-void
-decoder_initialized(Decoder &decoder,
- const AudioFormat audio_format,
- bool seekable, float total_time)
-{
- DecoderControl &dc = decoder.dc;
- struct audio_format_string af_string;
-
- assert(dc.state == DecoderState::START);
- assert(dc.pipe != nullptr);
- assert(dc.pipe->IsEmpty());
- assert(decoder.stream_tag == nullptr);
- assert(decoder.decoder_tag == nullptr);
- 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 = DecoderState::DECODE;
- dc.client_cond.signal();
- dc.Unlock();
-
- FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
- audio_format_to_string(dc.in_audio_format, &af_string),
- seekable ? "true" : "false");
-
- if (dc.in_audio_format != dc.out_audio_format)
- FormatDebug(decoder_domain, "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.
- */
-gcc_pure
-static bool
-decoder_prepare_initial_seek(Decoder &decoder)
-{
- const DecoderControl &dc = decoder.dc;
- assert(dc.pipe != nullptr);
-
- if (dc.state != DecoderState::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 == DecoderCommand::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.
- */
-gcc_pure
-static DecoderCommand
-decoder_get_virtual_command(Decoder &decoder)
-{
- const DecoderControl &dc = decoder.dc;
- assert(dc.pipe != nullptr);
-
- if (decoder_prepare_initial_seek(decoder))
- return DecoderCommand::SEEK;
-
- return dc.command;
-}
-
-DecoderCommand
-decoder_get_command(Decoder &decoder)
-{
- return decoder_get_virtual_command(decoder);
-}
-
-void
-decoder_command_finished(Decoder &decoder)
-{
- DecoderControl &dc = decoder.dc;
-
- dc.Lock();
-
- assert(dc.command != DecoderCommand::NONE ||
- decoder.initial_seek_running);
- assert(dc.command != DecoderCommand::SEEK ||
- decoder.initial_seek_running ||
- dc.seek_error || decoder.seeking);
- assert(dc.pipe != nullptr);
-
- if (decoder.initial_seek_running) {
- assert(!decoder.seeking);
- assert(decoder.chunk == nullptr);
- assert(dc.pipe->IsEmpty());
-
- 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 != nullptr) {
- dc.buffer->Return(decoder.chunk);
- decoder.chunk = nullptr;
- }
-
- dc.pipe->Clear(*dc.buffer);
-
- decoder.timestamp = dc.seek_where;
- }
-
- dc.command = DecoderCommand::NONE;
- dc.client_cond.signal();
- dc.Unlock();
-}
-
-double decoder_seek_where(gcc_unused Decoder & decoder)
-{
- const DecoderControl &dc = decoder.dc;
-
- assert(dc.pipe != nullptr);
-
- if (decoder.initial_seek_running)
- return dc.start_ms / 1000.;
-
- assert(dc.command == DecoderCommand::SEEK);
-
- decoder.seeking = true;
-
- return dc.seek_where;
-}
-
-void decoder_seek_error(Decoder & decoder)
-{
- DecoderControl &dc = decoder.dc;
-
- assert(dc.pipe != nullptr);
-
- 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 == DecoderCommand::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".
- */
-gcc_pure
-static inline bool
-decoder_check_cancel_read(const Decoder *decoder)
-{
- if (decoder == nullptr)
- return false;
-
- const DecoderControl &dc = decoder->dc;
- if (dc.command == DecoderCommand::NONE)
- return false;
-
- /* ignore the SEEK command during initialization, the plugin
- should handle that after it has initialized successfully */
- if (dc.command == DecoderCommand::SEEK &&
- (dc.state == DecoderState::START || decoder->seeking))
- return false;
-
- return true;
-}
-
-size_t
-decoder_read(Decoder *decoder,
- InputStream &is,
- void *buffer, size_t length)
-{
- /* XXX don't allow decoder==nullptr */
-
- assert(decoder == nullptr ||
- decoder->dc.state == DecoderState::START ||
- decoder->dc.state == DecoderState::DECODE);
- assert(buffer != nullptr);
-
- if (length == 0)
- return 0;
-
- is.Lock();
-
- while (true) {
- if (decoder_check_cancel_read(decoder)) {
- is.Unlock();
- return 0;
- }
-
- if (is.IsAvailable())
- break;
-
- is.cond.wait(is.mutex);
- }
-
- Error error;
- size_t nbytes = is.Read(buffer, length, error);
- assert(nbytes == 0 || !error.IsDefined());
- assert(nbytes > 0 || error.IsDefined() || is.IsEOF());
-
- is.Unlock();
-
- if (gcc_unlikely(nbytes == 0 && error.IsDefined()))
- LogError(error);
-
- return nbytes;
-}
-
-bool
-decoder_read_full(Decoder *decoder, InputStream &is,
- void *_buffer, size_t size)
-{
- uint8_t *buffer = (uint8_t *)_buffer;
-
- while (size > 0) {
- size_t nbytes = decoder_read(decoder, is, buffer, size);
- if (nbytes == 0)
- return false;
-
- buffer += nbytes;
- size -= nbytes;
- }
-
- return true;
-}
-
-bool
-decoder_skip(Decoder *decoder, InputStream &is, size_t size)
-{
- while (size > 0) {
- char buffer[1024];
- size_t nbytes = decoder_read(decoder, is, buffer,
- std::min(sizeof(buffer), size));
- if (nbytes == 0)
- return false;
-
- size -= nbytes;
- }
-
- return true;
-}
-
-void
-decoder_timestamp(Decoder &decoder, double t)
-{
- 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 DecoderCommand
-do_send_tag(Decoder &decoder, const Tag &tag)
-{
- struct music_chunk *chunk;
-
- if (decoder.chunk != nullptr) {
- /* there is a partial chunk - flush it, we want the
- tag in a new chunk */
- decoder_flush_chunk(decoder);
- }
-
- assert(decoder.chunk == nullptr);
-
- chunk = decoder_get_chunk(decoder);
- if (chunk == nullptr) {
- assert(decoder.dc.command != DecoderCommand::NONE);
- return decoder.dc.command;
- }
-
- chunk->tag = new Tag(tag);
- return DecoderCommand::NONE;
-}
-
-static bool
-update_stream_tag(Decoder &decoder, InputStream *is)
-{
- Tag *tag;
-
- tag = is != nullptr
- ? is->LockReadTag()
- : nullptr;
- if (tag == nullptr) {
- tag = decoder.song_tag;
- if (tag == nullptr)
- return false;
-
- /* no stream tag present - submit the song tag
- instead */
- decoder.song_tag = nullptr;
- }
-
- delete decoder.stream_tag;
- decoder.stream_tag = tag;
- return true;
-}
-
-DecoderCommand
-decoder_data(Decoder &decoder,
- InputStream *is,
- const void *data, size_t length,
- uint16_t kbit_rate)
-{
- DecoderControl &dc = decoder.dc;
- DecoderCommand cmd;
-
- assert(dc.state == DecoderState::DECODE);
- assert(dc.pipe != nullptr);
- assert(length % dc.in_audio_format.GetFrameSize() == 0);
-
- dc.Lock();
- cmd = decoder_get_virtual_command(decoder);
- dc.Unlock();
-
- if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK ||
- length == 0)
- return cmd;
-
- assert(!decoder.initial_seek_pending);
- assert(!decoder.initial_seek_running);
-
- /* send stream tags */
-
- if (update_stream_tag(decoder, is)) {
- if (decoder.decoder_tag != nullptr) {
- /* 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 != DecoderCommand::NONE)
- return cmd;
- }
-
- if (dc.in_audio_format != dc.out_audio_format) {
- Error error;
- data = decoder.conv_state.Convert(dc.in_audio_format,
- data, length,
- dc.out_audio_format,
- &length,
- error);
- if (data == nullptr) {
- /* the PCM conversion has failed - stop
- playback, since we have no better way to
- bail out */
- LogError(error);
- return DecoderCommand::STOP;
- }
- }
-
- while (length > 0) {
- struct music_chunk *chunk;
- bool full;
-
- chunk = decoder_get_chunk(decoder);
- if (chunk == nullptr) {
- assert(dc.command != DecoderCommand::NONE);
- return dc.command;
- }
-
- const auto dest =
- chunk->Write(dc.out_audio_format,
- decoder.timestamp -
- dc.song->start_ms / 1000.0,
- kbit_rate);
- if (dest.IsNull()) {
- /* the chunk is full, flush it */
- decoder_flush_chunk(decoder);
- continue;
- }
-
- size_t nbytes = dest.size;
- assert(nbytes > 0);
-
- if (nbytes > length)
- nbytes = length;
-
- /* copy the buffer */
-
- memcpy(dest.data, 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);
- }
-
- 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 DecoderCommand::STOP;
- }
-
- return DecoderCommand::NONE;
-}
-
-DecoderCommand
-decoder_tag(Decoder &decoder, InputStream *is,
- Tag &&tag)
-{
- gcc_unused const DecoderControl &dc = decoder.dc;
- DecoderCommand cmd;
-
- assert(dc.state == DecoderState::DECODE);
- assert(dc.pipe != nullptr);
-
- /* 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 DecoderCommand::SEEK;
-
- /* send tag to music pipe */
-
- if (decoder.stream_tag != nullptr) {
- /* 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(Decoder &decoder,
- const ReplayGainInfo *replay_gain_info)
-{
- if (replay_gain_info != nullptr) {
- static unsigned serial;
- if (++serial == 0)
- serial = 1;
-
- if (REPLAY_GAIN_OFF != replay_gain_mode) {
- ReplayGainMode rgm = replay_gain_mode;
- if (rgm != REPLAY_GAIN_ALBUM)
- rgm = REPLAY_GAIN_TRACK;
-
- const auto &tuple = replay_gain_info->tuples[rgm];
- const auto scale =
- tuple.CalculateScale(replay_gain_preamp,
- replay_gain_missing_preamp,
- replay_gain_limit);
- decoder.dc.replay_gain_db = 20.0 * log10f(scale);
- }
-
- decoder.replay_gain_info = *replay_gain_info;
- decoder.replay_gain_serial = serial;
-
- if (decoder.chunk != nullptr) {
- /* flush the current chunk because the new
- replay gain values affect the following
- samples */
- decoder_flush_chunk(decoder);
- }
- } else
- decoder.replay_gain_serial = 0;
-}
-
-void
-decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp)
-{
- DecoderControl &dc = decoder.dc;
-
- dc.SetMixRamp(std::move(mix_ramp));
-}
diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx
deleted file mode 100644
index a9da76305..000000000
--- a/src/DecoderAPI.hxx
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*! \file
- * \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 "ReplayGainInfo.hxx"
-#include "tag/Tag.hxx"
-#include "AudioFormat.hxx"
-#include "MixRampInfo.hxx"
-#include "ConfigData.hxx"
-
-/**
- * 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(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 DecoderCommand::NONE if there is no
- * command pending
- */
-gcc_pure
-DecoderCommand
-decoder_get_command(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(Decoder &decoder);
-
-/**
- * Call this when you have received the DecoderCommand::SEEK command.
- *
- * @param decoder the decoder object
- * @return the destination position for the week
- */
-gcc_pure
-double
-decoder_seek_where(Decoder &decoder);
-
-/**
- * Call this instead of decoder_command_finished() when seeking has
- * failed.
- *
- * @param decoder the decoder object
- */
-void
-decoder_seek_error(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(Decoder *decoder, InputStream &is,
- void *buffer, size_t length);
-
-static inline size_t
-decoder_read(Decoder &decoder, InputStream &is,
- void *buffer, size_t length)
-{
- return decoder_read(&decoder, is, buffer, length);
-}
-
-/**
- * Blocking read from the input stream. Attempts to fill the buffer
- * completely; there is no partial result.
- *
- * @return true on success, false on error or command or not enough
- * data
- */
-bool
-decoder_read_full(Decoder *decoder, InputStream &is,
- void *buffer, size_t size);
-
-/**
- * Skip data on the #InputStream.
- *
- * @return true on success, false on error or command
- */
-bool
-decoder_skip(Decoder *decoder, InputStream &is, size_t size);
-
-/**
- * Sets the time stamp for the next data chunk [seconds]. The MPD
- * core automatically counts it up, and a decoder plugin only needs to
- * use this function if it thinks that adding to the time stamp based
- * on the buffer size won't work.
- */
-void
-decoder_timestamp(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 DecoderCommand::NONE if there is no
- * command pending
- */
-DecoderCommand
-decoder_data(Decoder &decoder, InputStream *is,
- const void *data, size_t length,
- uint16_t kbit_rate);
-
-static inline DecoderCommand
-decoder_data(Decoder &decoder, InputStream &is,
- const void *data, size_t length,
- uint16_t kbit_rate)
-{
- return decoder_data(decoder, &is, data, length, 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 DecoderCommand::NONE if there is no
- * command pending
- */
-DecoderCommand
-decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag);
-
-static inline DecoderCommand
-decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag)
-{
- return decoder_tag(decoder, &is, std::move(tag));
-}
-
-/**
- * Set replay gain values for the following chunks.
- *
- * @param decoder the decoder object
- * @param rgi the replay_gain_info object; may be nullptr to invalidate
- * the previous replay gain values
- */
-void
-decoder_replay_gain(Decoder &decoder,
- const ReplayGainInfo *replay_gain_info);
-
-/**
- * Store MixRamp tags.
- *
- * @param decoder the decoder object
- * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate
- * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate
- */
-void
-decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp);
-
-#endif
diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx
deleted file mode 100644
index 4a5125bc5..000000000
--- a/src/DecoderBuffer.cxx
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderBuffer.hxx"
-#include "DecoderAPI.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct DecoderBuffer {
- Decoder *decoder;
- InputStream *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(Decoder *decoder, InputStream &is,
- size_t size)
-{
- DecoderBuffer *buffer = (DecoderBuffer *)
- g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size);
-
- assert(size > 0);
-
- buffer->decoder = decoder;
- buffer->is = &is;
- buffer->size = size;
- buffer->length = 0;
- buffer->consumed = 0;
-
- return buffer;
-}
-
-void
-decoder_buffer_free(DecoderBuffer *buffer)
-{
- assert(buffer != nullptr);
-
- g_free(buffer);
-}
-
-const InputStream &
-decoder_buffer_get_stream(const DecoderBuffer *buffer)
-{
- return *buffer->is;
-}
-
-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;
-}
-
-void
-decoder_buffer_clear(DecoderBuffer *buffer)
-{
- buffer->length = buffer->consumed = 0;
-}
-
-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;
-}
-
-size_t
-decoder_buffer_available(const DecoderBuffer *buffer)
-{
- return buffer->length - buffer->consumed;;
-}
-
-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
deleted file mode 100644
index 65c6e0d2e..000000000
--- a/src/DecoderBuffer.hxx
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_BUFFER_HXX
-#define MPD_DECODER_BUFFER_HXX
-
-#include "Compiler.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 DecoderBuffer;
-
-struct Decoder;
-struct InputStream;
-
-/**
- * Creates a new buffer.
- *
- * @param decoder the decoder object, used for decoder_read(), may be nullptr
- * @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(Decoder *decoder, InputStream &is,
- size_t size);
-
-/**
- * Frees resources used by the decoder_buffer object.
- */
-void
-decoder_buffer_free(DecoderBuffer *buffer);
-
-gcc_pure
-const InputStream &
-decoder_buffer_get_stream(const DecoderBuffer *buffer);
-
-gcc_pure
-bool
-decoder_buffer_is_empty(const DecoderBuffer *buffer);
-
-gcc_pure
-bool
-decoder_buffer_is_full(const DecoderBuffer *buffer);
-
-void
-decoder_buffer_clear(DecoderBuffer *buffer);
-
-/**
- * Read data from the input_stream and append it to the buffer.
- *
- * @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);
-
-/**
- * How many bytes are stored in the buffer?
- */
-gcc_pure
-size_t
-decoder_buffer_available(const 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 nullptr 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
deleted file mode 100644
index 394f270c2..000000000
--- a/src/DecoderCommand.hxx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_COMMAND_HXX
-#define MPD_DECODER_COMMAND_HXX
-
-#include <stdint.h>
-
-enum class DecoderCommand : uint8_t {
- NONE = 0,
- START,
- STOP,
- SEEK
-};
-
-#endif
diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx
deleted file mode 100644
index ab460ced0..000000000
--- a/src/DecoderControl.cxx
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderControl.hxx"
-#include "MusicPipe.hxx"
-#include "Song.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-
-DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond)
- :mutex(_mutex), client_cond(_client_cond),
- state(DecoderState::STOP),
- command(DecoderCommand::NONE),
- client_is_waiting(false),
- song(nullptr),
- replay_gain_db(0), replay_gain_prev_db(0) {}
-
-DecoderControl::~DecoderControl()
-{
- ClearError();
-
- if (song != nullptr)
- song->Free();
-}
-
-void
-DecoderControl::WaitForDecoder()
-{
- assert(!client_is_waiting);
- client_is_waiting = true;
-
- client_cond.wait(mutex);
-
- assert(client_is_waiting);
- client_is_waiting = false;
-}
-
-bool
-DecoderControl::IsCurrentSong(const Song &_song) const
-{
- switch (state) {
- case DecoderState::STOP:
- case DecoderState::ERROR:
- return false;
-
- case DecoderState::START:
- case DecoderState::DECODE:
- return SongEquals(*song, _song);
- }
-
- assert(false);
- gcc_unreachable();
-}
-
-void
-DecoderControl::Start(Song *_song,
- unsigned _start_ms, unsigned _end_ms,
- MusicBuffer &_buffer, MusicPipe &_pipe)
-{
- assert(_song != nullptr);
- assert(_pipe.IsEmpty());
-
- if (song != nullptr)
- song->Free();
-
- song = _song;
- start_ms = _start_ms;
- end_ms = _end_ms;
- buffer = &_buffer;
- pipe = &_pipe;
-
- LockSynchronousCommand(DecoderCommand::START);
-}
-
-void
-DecoderControl::Stop()
-{
- Lock();
-
- if (command != DecoderCommand::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). */
- SynchronousCommandLocked(DecoderCommand::STOP);
-
- if (state != DecoderState::STOP && state != DecoderState::ERROR)
- SynchronousCommandLocked(DecoderCommand::STOP);
-
- Unlock();
-}
-
-bool
-DecoderControl::Seek(double where)
-{
- assert(state != DecoderState::START);
- assert(where >= 0.0);
-
- if (state == DecoderState::STOP ||
- state == DecoderState::ERROR || !seekable)
- return false;
-
- seek_where = where;
- seek_error = false;
- LockSynchronousCommand(DecoderCommand::SEEK);
-
- return !seek_error;
-}
-
-void
-DecoderControl::Quit()
-{
- assert(thread.IsDefined());
-
- quit = true;
- LockAsynchronousCommand(DecoderCommand::STOP);
-
- thread.Join();
-}
-
-void
-DecoderControl::CycleMixRamp()
-{
- previous_mix_ramp = std::move(mix_ramp);
- mix_ramp.Clear();
-}
diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx
deleted file mode 100644
index 863398dca..000000000
--- a/src/DecoderControl.hxx
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_CONTROL_HXX
-#define MPD_DECODER_CONTROL_HXX
-
-#include "DecoderCommand.hxx"
-#include "AudioFormat.hxx"
-#include "MixRampInfo.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "thread/Thread.hxx"
-#include "util/Error.hxx"
-
-#include <assert.h>
-#include <stdint.h>
-
-/* damn you, windows.h! */
-#ifdef ERROR
-#undef ERROR
-#endif
-
-struct Song;
-class MusicBuffer;
-class MusicPipe;
-
-enum class DecoderState : uint8_t {
- STOP = 0,
- START,
- 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.
- */
- ERROR,
-};
-
-struct DecoderControl {
- /**
- * The handle of the decoder thread.
- */
- Thread thread;
-
- /**
- * This lock protects #state and #command.
- *
- * This is usually a reference to PlayerControl::mutex, so
- * that both player thread and decoder thread share a mutex.
- * This simplifies synchronization with #cond and
- * #client_cond.
- */
- 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.
- *
- * This is usually a reference to PlayerControl::cond.
- */
- Cond &client_cond;
-
- DecoderState state;
- DecoderCommand command;
-
- /**
- * The error that occurred in the decoder thread. This
- * attribute is only valid if #state is #DecoderState::ERROR.
- * The object must be freed when this object transitions to
- * any other state (usually #DecoderState::START).
- */
- Error error;
-
- bool quit;
-
- /**
- * Is the client currently waiting for the DecoderThread? If
- * false, the DecoderThread may omit invoking Cond::signal(),
- * reducing the number of system calls.
- */
- bool client_is_waiting;
-
- 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 #DecoderCommand::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 */
- MusicBuffer *buffer;
-
- /**
- * The destination pipe for decoded chunks. The caller thread
- * owns this object, and is responsible for freeing it.
- */
- MusicPipe *pipe;
-
- float replay_gain_db;
- float replay_gain_prev_db;
-
- MixRampInfo mix_ramp, previous_mix_ramp;
-
- /**
- * @param _mutex see #mutex
- * @param _client_cond see #client_cond
- */
- DecoderControl(Mutex &_mutex, Cond &_client_cond);
- ~DecoderControl();
-
- /**
- * 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 #DecoderControl 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.
- *
- * Caller must hold the lock.
- */
- void WaitForDecoder();
-
- bool IsIdle() const {
- return state == DecoderState::STOP ||
- state == DecoderState::ERROR;
- }
-
- gcc_pure
- bool LockIsIdle() const {
- Lock();
- bool result = IsIdle();
- Unlock();
- return result;
- }
-
- bool IsStarting() const {
- return state == DecoderState::START;
- }
-
- gcc_pure
- bool LockIsStarting() const {
- Lock();
- bool result = IsStarting();
- Unlock();
- return result;
- }
-
- bool HasFailed() const {
- assert(command == DecoderCommand::NONE);
-
- return state == DecoderState::ERROR;
- }
-
- gcc_pure
- bool LockHasFailed() const {
- Lock();
- bool result = HasFailed();
- Unlock();
- return result;
- }
-
- /**
- * Checks whether an error has occurred, and if so, returns a
- * copy of the #Error object.
- *
- * Caller must lock the object.
- */
- gcc_pure
- Error GetError() const {
- assert(command == DecoderCommand::NONE);
- assert(state != DecoderState::ERROR || error.IsDefined());
-
- Error result;
- if (state == DecoderState::ERROR)
- result.Set(error);
- return result;
- }
-
- /**
- * Like dc_get_error(), but locks and unlocks the object.
- */
- gcc_pure
- Error LockGetError() const {
- Lock();
- Error result = GetError();
- Unlock();
- return result;
- }
-
- /**
- * Clear the error condition and free the #Error object (if any).
- *
- * Caller must lock the object.
- */
- void ClearError() {
- if (state == DecoderState::ERROR) {
- error.Clear();
- state = DecoderState::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;
- }
-
-private:
- /**
- * Wait for the command to be finished by the decoder thread.
- *
- * To be called from the client thread. Caller must lock the
- * object.
- */
- void WaitCommandLocked() {
- while (command != DecoderCommand::NONE)
- WaitForDecoder();
- }
-
- /**
- * Send a command to the decoder thread and synchronously wait
- * for it to finish.
- *
- * To be called from the client thread. Caller must lock the
- * object.
- */
- void SynchronousCommandLocked(DecoderCommand cmd) {
- command = cmd;
- Signal();
- WaitCommandLocked();
- }
-
- /**
- * Send a command to the decoder thread and synchronously wait
- * for it to finish.
- *
- * To be called from the client thread. This method locks the
- * object.
- */
- void LockSynchronousCommand(DecoderCommand cmd) {
- Lock();
- ClearError();
- SynchronousCommandLocked(cmd);
- Unlock();
- }
-
- void LockAsynchronousCommand(DecoderCommand cmd) {
- Lock();
- command = cmd;
- Signal();
- Unlock();
- }
-
-public:
- /**
- * 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 #DecoderControl
- * @param end_ms see #DecoderControl
- * @param pipe the pipe which receives the decoded chunks (owned by
- * the caller)
- */
- void Start(Song *song, unsigned start_ms, unsigned end_ms,
- MusicBuffer &buffer, MusicPipe &pipe);
-
- void Stop();
-
- bool Seek(double where);
-
- void Quit();
-
- const char *GetMixRampStart() const {
- return mix_ramp.GetStart();
- }
-
- const char *GetMixRampEnd() const {
- return mix_ramp.GetEnd();
- }
-
- const char *GetMixRampPreviousEnd() const {
- return previous_mix_ramp.GetEnd();
- }
-
- void SetMixRamp(MixRampInfo &&new_value) {
- mix_ramp = std::move(new_value);
- }
-
- /**
- * Move mixramp_end to mixramp_prev_end and clear
- * mixramp_start/mixramp_end.
- */
- void CycleMixRamp();
-};
-
-#endif
diff --git a/src/DecoderError.cxx b/src/DecoderError.cxx
deleted file mode 100644
index a8d3f548d..000000000
--- a/src/DecoderError.cxx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "DecoderError.hxx"
-#include "util/Domain.hxx"
-
-const Domain decoder_domain("decoder");
diff --git a/src/DecoderError.hxx b/src/DecoderError.hxx
deleted file mode 100644
index 9ea74167f..000000000
--- a/src/DecoderError.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_ERROR_HXX
-#define MPD_DECODER_ERROR_HXX
-
-extern const class Domain decoder_domain;
-
-#endif
diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx
deleted file mode 100644
index b5e6c9d57..000000000
--- a/src/DecoderInternal.cxx
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderInternal.hxx"
-#include "DecoderControl.hxx"
-#include "MusicPipe.hxx"
-#include "MusicBuffer.hxx"
-#include "MusicChunk.hxx"
-#include "tag/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 DecoderCommand
-need_chunks(DecoderControl &dc)
-{
- if (dc.command == DecoderCommand::NONE)
- dc.Wait();
-
- return dc.command;
-}
-
-struct music_chunk *
-decoder_get_chunk(Decoder &decoder)
-{
- DecoderControl &dc = decoder.dc;
- DecoderCommand cmd;
-
- if (decoder.chunk != nullptr)
- return decoder.chunk;
-
- do {
- decoder.chunk = dc.buffer->Allocate();
- if (decoder.chunk != nullptr) {
- decoder.chunk->replay_gain_serial =
- decoder.replay_gain_serial;
- if (decoder.replay_gain_serial != 0)
- decoder.chunk->replay_gain_info =
- decoder.replay_gain_info;
-
- return decoder.chunk;
- }
-
- dc.Lock();
- cmd = need_chunks(dc);
- dc.Unlock();
- } while (cmd == DecoderCommand::NONE);
-
- return nullptr;
-}
-
-void
-decoder_flush_chunk(Decoder &decoder)
-{
- DecoderControl &dc = decoder.dc;
- assert(!decoder.seeking);
- assert(!decoder.initial_seek_running);
- assert(!decoder.initial_seek_pending);
-
- assert(decoder.chunk != nullptr);
-
- if (decoder.chunk->IsEmpty())
- dc.buffer->Return(decoder.chunk);
- else
- dc.pipe->Push(decoder.chunk);
-
- decoder.chunk = nullptr;
-
- dc.Lock();
- if (dc.client_is_waiting)
- dc.client_cond.signal();
- dc.Unlock();
-}
diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx
deleted file mode 100644
index 46069a561..000000000
--- a/src/DecoderInternal.hxx
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_INTERNAL_HXX
-#define MPD_DECODER_INTERNAL_HXX
-
-#include "DecoderCommand.hxx"
-#include "pcm/PcmConvert.hxx"
-#include "ReplayGainInfo.hxx"
-
-struct DecoderControl;
-struct InputStream;
-struct Tag;
-
-struct Decoder {
- DecoderControl &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;
-
- ReplayGainInfo replay_gain_info;
-
- /**
- * A positive serial number for checking if replay gain info
- * has changed since the last check.
- */
- unsigned replay_gain_serial;
-
- Decoder(DecoderControl &_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(Decoder &decoder);
-
-/**
- * Flushes the current chunk.
- *
- * Caller must not lock the #DecoderControl object.
- */
-void
-decoder_flush_chunk(Decoder &decoder);
-
-#endif
diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx
deleted file mode 100644
index 4546cac2f..000000000
--- a/src/DecoderList.cxx
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderList.hxx"
-#include "DecoderPlugin.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
-#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 "decoder/SidplayDecoderPlugin.hxx"
-#include "system/FatalError.hxx"
-#include "util/Macros.hxx"
-
-#include <string.h>
-
-const struct DecoderPlugin *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
-#ifdef ENABLE_DSD
- &dsdiff_decoder_plugin,
- &dsf_decoder_plugin,
-#endif
-#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,
- nullptr
-};
-
-static constexpr unsigned num_decoder_plugins =
- ARRAY_SIZE(decoder_plugins) - 1;
-
-/** which plugins have been initialized successfully? */
-bool decoder_plugins_enabled[num_decoder_plugins];
-
-static unsigned
-decoder_plugin_index(const struct DecoderPlugin *plugin)
-{
- unsigned i = 0;
-
- while (decoder_plugins[i] != plugin)
- ++i;
-
- return i;
-}
-
-static unsigned
-decoder_plugin_next_index(const struct DecoderPlugin *plugin)
-{
- return plugin == 0
- ? 0 /* start with first plugin */
- : decoder_plugin_index(plugin) + 1;
-}
-
-const struct DecoderPlugin *
-decoder_plugin_from_suffix(const char *suffix,
- const struct DecoderPlugin *plugin)
-{
- if (suffix == nullptr)
- return nullptr;
-
- for (unsigned i = decoder_plugin_next_index(plugin);
- decoder_plugins[i] != nullptr; ++i) {
- plugin = decoder_plugins[i];
- if (decoder_plugins_enabled[i] &&
- plugin->SupportsSuffix(suffix))
- return plugin;
- }
-
- return nullptr;
-}
-
-const struct DecoderPlugin *
-decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
-{
- static unsigned i = num_decoder_plugins;
-
- if (mimeType == nullptr)
- return nullptr;
-
- if (!next)
- i = 0;
- for (; decoder_plugins[i] != nullptr; ++i) {
- const struct DecoderPlugin *plugin = decoder_plugins[i];
- if (decoder_plugins_enabled[i] &&
- plugin->SupportsMimeType(mimeType)) {
- ++i;
- return plugin;
- }
- }
-
- return nullptr;
-}
-
-const struct DecoderPlugin *
-decoder_plugin_from_name(const char *name)
-{
- return decoder_plugins_find([=](const DecoderPlugin &plugin){
- return strcmp(plugin.name, name) == 0;
- });
-}
-
-/**
- * Find the "decoder" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the decoder plugin
- * @return the configuration block, or nullptr if none was configured
- */
-static const struct config_param *
-decoder_plugin_config(const char *plugin_name)
-{
- const struct config_param *param = nullptr;
-
- while ((param = config_get_next_param(CONF_DECODER, param)) != nullptr) {
- const char *name = param->GetBlockValue("plugin");
- if (name == nullptr)
- FormatFatalError("decoder configuration without 'plugin' name in line %d",
- param->line);
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return nullptr;
-}
-
-void decoder_plugin_init_all(void)
-{
- struct config_param empty;
-
- for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) {
- const DecoderPlugin &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 (plugin.Init(*param))
- decoder_plugins_enabled[i] = true;
- }
-}
-
-void decoder_plugin_deinit_all(void)
-{
- decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
- plugin.Finish();
- });
-}
diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx
deleted file mode 100644
index fd4b22c63..000000000
--- a/src/DecoderList.hxx
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_LIST_HXX
-#define MPD_DECODER_LIST_HXX
-
-struct DecoderPlugin;
-
-extern const struct DecoderPlugin *const decoder_plugins[];
-extern bool decoder_plugins_enabled[];
-
-/* interface for using plugins */
-
-/**
- * Find the next enabled decoder plugin which supports the specified suffix.
- *
- * @param suffix the file name suffix
- * @param plugin the previous plugin, or nullptr to find the first plugin
- * @return a plugin, or nullptr if none matches
- */
-const struct DecoderPlugin *
-decoder_plugin_from_suffix(const char *suffix,
- const struct DecoderPlugin *plugin);
-
-const struct DecoderPlugin *
-decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
-
-const struct DecoderPlugin *
-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);
-
-template<typename F>
-static inline const DecoderPlugin *
-decoder_plugins_find(F f)
-{
- for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
- if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
- return decoder_plugins[i];
-
- return nullptr;
-}
-
-template<typename F>
-static inline bool
-decoder_plugins_try(F f)
-{
- for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
- if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
- return true;
-
- return false;
-}
-
-template<typename F>
-static inline void
-decoder_plugins_for_each(F f)
-{
- for (auto i = decoder_plugins; *i != nullptr; ++i)
- f(**i);
-}
-
-template<typename F>
-static inline void
-decoder_plugins_for_each_enabled(F f)
-{
- for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
- if (decoder_plugins_enabled[i])
- f(*decoder_plugins[i]);
-}
-
-#endif
diff --git a/src/DecoderPlugin.cxx b/src/DecoderPlugin.cxx
deleted file mode 100644
index 5170555f5..000000000
--- a/src/DecoderPlugin.cxx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderPlugin.hxx"
-#include "util/StringUtil.hxx"
-
-#include <assert.h>
-
-bool
-DecoderPlugin::SupportsSuffix(const char *suffix) const
-{
-#if !CLANG_CHECK_VERSION(3,6)
- /* disabled on clang due to -Wtautological-pointer-compare */
- assert(suffix != nullptr);
-#endif
-
- return suffixes != nullptr && string_array_contains(suffixes, suffix);
-
-}
-
-bool
-DecoderPlugin::SupportsMimeType(const char *mime_type) const
-{
-#if !CLANG_CHECK_VERSION(3,6)
- /* disabled on clang due to -Wtautological-pointer-compare */
- assert(mime_type != nullptr);
-#endif
-
- return mime_types != nullptr &&
- string_array_contains(mime_types, mime_type);
-}
diff --git a/src/DecoderPlugin.hxx b/src/DecoderPlugin.hxx
deleted file mode 100644
index 6b0340123..000000000
--- a/src/DecoderPlugin.hxx
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_PLUGIN_HXX
-#define MPD_DECODER_PLUGIN_HXX
-
-#include "Compiler.h"
-
-struct config_param;
-struct InputStream;
-struct Tag;
-struct tag_handler;
-
-/**
- * Opaque handle which the decoder plugin passes to the functions in
- * this header.
- */
-struct Decoder;
-
-struct DecoderPlugin {
- 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)(Decoder &decoder, InputStream &is);
-
- /**
- * Decode a local file.
- *
- * Either implement this method or stream_decode().
- */
- void (*file_decode)(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)(InputStream &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
- */
- bool Init(const config_param &param) const {
- return init != nullptr
- ? init(param)
- : true;
- }
-
- /**
- * Deinitialize a decoder plugin which was initialized successfully.
- */
- void Finish() const {
- if (finish != nullptr)
- finish();
- }
-
- /**
- * Decode a stream.
- */
- void StreamDecode(Decoder &decoder, InputStream &is) const {
- stream_decode(decoder, is);
- }
-
- /**
- * Decode a file.
- */
- void FileDecode(Decoder &decoder, const char *path_fs) const {
- file_decode(decoder, path_fs);
- }
-
- /**
- * Read the tag of a file.
- */
- bool ScanFile(const char *path_fs,
- const tag_handler &handler, void *handler_ctx) const {
- return scan_file != nullptr
- ? scan_file(path_fs, &handler, handler_ctx)
- : false;
- }
-
- /**
- * Read the tag of a stream.
- */
- bool ScanStream(InputStream &is,
- const tag_handler &handler, void *handler_ctx) const {
- return scan_stream != nullptr
- ? scan_stream(is, &handler, handler_ctx)
- : false;
- }
-
- /**
- * return "virtual" tracks in a container
- */
- char *ContainerScan(const char *path, const unsigned int tnum) const {
- return container_scan(path, tnum);
- }
-
- /**
- * Does the plugin announce the specified file name suffix?
- */
- gcc_pure gcc_nonnull_all
- bool SupportsSuffix(const char *suffix) const;
-
- /**
- * Does the plugin announce the specified MIME type?
- */
- gcc_pure gcc_nonnull_all
- bool SupportsMimeType(const char *mime_type) const;
-};
-
-#endif
diff --git a/src/DecoderPrint.cxx b/src/DecoderPrint.cxx
deleted file mode 100644
index 2372272c2..000000000
--- a/src/DecoderPrint.cxx
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderPrint.hxx"
-#include "DecoderList.hxx"
-#include "DecoderPlugin.hxx"
-#include "Client.hxx"
-
-#include <functional>
-
-#include <assert.h>
-
-static void
-decoder_plugin_print(Client &client,
- const DecoderPlugin &plugin)
-{
- const char *const*p;
-
- assert(plugin.name != nullptr);
-
- client_printf(client, "plugin: %s\n", plugin.name);
-
- if (plugin.suffixes != nullptr)
- for (p = plugin.suffixes; *p != nullptr; ++p)
- client_printf(client, "suffix: %s\n", *p);
-
- if (plugin.mime_types != nullptr)
- for (p = plugin.mime_types; *p != nullptr; ++p)
- client_printf(client, "mime_type: %s\n", *p);
-}
-
-void
-decoder_list_print(Client &client)
-{
- using namespace std::placeholders;
- const auto f = std::bind(decoder_plugin_print, std::ref(client), _1);
- decoder_plugins_for_each_enabled(f);
-}
diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx
deleted file mode 100644
index 693608746..000000000
--- a/src/DecoderPrint.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 7ee36faca..000000000
--- a/src/DecoderThread.cxx
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DecoderThread.hxx"
-#include "DecoderControl.hxx"
-#include "DecoderInternal.hxx"
-#include "DecoderError.hxx"
-#include "DecoderPlugin.hxx"
-#include "Song.hxx"
-#include "system/FatalError.hxx"
-#include "Mapper.hxx"
-#include "MusicPipe.hxx"
-#include "fs/Traits.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "DecoderAPI.hxx"
-#include "tag/Tag.hxx"
-#include "InputStream.hxx"
-#include "DecoderList.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "tag/ApeReplayGain.hxx"
-#include "Log.hxx"
-
-#include <functional>
-
-static constexpr Domain decoder_thread_domain("decoder_thread");
-
-/**
- * Marks the current decoder command as "finished" and notifies the
- * player thread.
- *
- * @param dc the #DecoderControl object; must be locked
- */
-static void
-decoder_command_finished_locked(DecoderControl &dc)
-{
- assert(dc.command != DecoderCommand::NONE);
-
- dc.command = DecoderCommand::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 #DecoderCommand::STOP is
- * received, nullptr on error
- */
-static InputStream *
-decoder_input_stream_open(DecoderControl &dc, const char *uri)
-{
- Error error;
-
- InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error);
- if (is == nullptr) {
- if (error.IsDefined())
- LogError(error);
-
- return nullptr;
- }
-
- /* wait for the input stream to become ready; its metadata
- will be available then */
-
- dc.Lock();
-
- is->Update();
- while (!is->ready &&
- dc.command != DecoderCommand::STOP) {
- dc.Wait();
-
- is->Update();
- }
-
- if (!is->Check(error)) {
- dc.Unlock();
-
- LogError(error);
- return nullptr;
- }
-
- dc.Unlock();
-
- return is;
-}
-
-static bool
-decoder_stream_decode(const DecoderPlugin &plugin,
- Decoder &decoder,
- InputStream &input_stream)
-{
- assert(plugin.stream_decode != nullptr);
- assert(decoder.stream_tag == nullptr);
- assert(decoder.decoder_tag == nullptr);
- assert(input_stream.ready);
- assert(decoder.dc.state == DecoderState::START);
-
- FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
-
- if (decoder.dc.command == DecoderCommand::STOP)
- return true;
-
- /* rewind the stream, so each plugin gets a fresh start */
- input_stream.Rewind(IgnoreError());
-
- decoder.dc.Unlock();
-
- plugin.StreamDecode(decoder, input_stream);
-
- decoder.dc.Lock();
-
- assert(decoder.dc.state == DecoderState::START ||
- decoder.dc.state == DecoderState::DECODE);
-
- return decoder.dc.state != DecoderState::START;
-}
-
-static bool
-decoder_file_decode(const DecoderPlugin &plugin,
- Decoder &decoder, const char *path)
-{
- assert(plugin.file_decode != nullptr);
- assert(decoder.stream_tag == nullptr);
- assert(decoder.decoder_tag == nullptr);
- assert(path != nullptr);
- assert(PathTraits::IsAbsoluteFS(path));
- assert(decoder.dc.state == DecoderState::START);
-
- FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
-
- if (decoder.dc.command == DecoderCommand::STOP)
- return true;
-
- decoder.dc.Unlock();
-
- plugin.FileDecode(decoder, path);
-
- decoder.dc.Lock();
-
- assert(decoder.dc.state == DecoderState::START ||
- decoder.dc.state == DecoderState::DECODE);
-
- return decoder.dc.state != DecoderState::START;
-}
-
-gcc_pure
-static bool
-decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is)
-{
- assert(plugin.stream_decode != nullptr);
-
- return !is.mime.empty() && plugin.SupportsMimeType(is.mime.c_str());
-}
-
-gcc_pure
-static bool
-decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix)
-{
- assert(plugin.stream_decode != nullptr);
-
- return suffix != nullptr && plugin.SupportsSuffix(suffix);
-}
-
-gcc_pure
-static bool
-decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is,
- const char *suffix)
-{
- return plugin.stream_decode != nullptr &&
- (decoder_check_plugin_mime(plugin, is) ||
- decoder_check_plugin_suffix(plugin, suffix));
-}
-
-static bool
-decoder_run_stream_plugin(Decoder &decoder, InputStream &is,
- const char *suffix,
- const DecoderPlugin &plugin,
- bool &tried_r)
-{
- if (!decoder_check_plugin(plugin, is, suffix))
- return false;
-
- tried_r = true;
- return decoder_stream_decode(plugin, decoder, is);
-}
-
-static bool
-decoder_run_stream_locked(Decoder &decoder, InputStream &is,
- const char *uri, bool &tried_r)
-{
- UriSuffixBuffer suffix_buffer;
- const char *const suffix = uri_get_suffix(uri, suffix_buffer);
-
- using namespace std::placeholders;
- const auto f = std::bind(decoder_run_stream_plugin,
- std::ref(decoder), std::ref(is), suffix,
- _1, std::ref(tried_r));
- return decoder_plugins_try(f);
-}
-
-/**
- * Try decoding a stream, using the fallback plugin.
- */
-static bool
-decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
-{
- const struct DecoderPlugin *plugin;
-
- plugin = decoder_plugin_from_name("mad");
- return plugin != nullptr && plugin->stream_decode != nullptr &&
- decoder_stream_decode(*plugin, decoder, is);
-}
-
-/**
- * Try decoding a stream.
- */
-static bool
-decoder_run_stream(Decoder &decoder, const char *uri)
-{
- DecoderControl &dc = decoder.dc;
- InputStream *input_stream;
- bool success;
-
- dc.Unlock();
-
- input_stream = decoder_input_stream_open(dc, uri);
- if (input_stream == nullptr) {
- dc.Lock();
- return false;
- }
-
- dc.Lock();
-
- bool tried = false;
- success = dc.command == DecoderCommand::STOP ||
- decoder_run_stream_locked(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 &&
- decoder_run_stream_fallback(decoder, *input_stream));
-
- dc.Unlock();
- input_stream->Close();
- dc.Lock();
-
- return success;
-}
-
-/**
- * Attempt to load replay gain data, and pass it to
- * decoder_replay_gain().
- */
-static void
-decoder_load_replay_gain(Decoder &decoder, const char *path_fs)
-{
- ReplayGainInfo info;
- if (replay_gain_ape_read(Path::FromFS(path_fs), info))
- decoder_replay_gain(decoder, &info);
-}
-
-/**
- * Try decoding a file.
- */
-static bool
-decoder_run_file(Decoder &decoder, const char *path_fs)
-{
- DecoderControl &dc = decoder.dc;
- const char *suffix = uri_get_suffix(path_fs);
- const struct DecoderPlugin *plugin = nullptr;
-
- if (suffix == nullptr)
- return false;
-
- dc.Unlock();
-
- decoder_load_replay_gain(decoder, path_fs);
-
- while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != nullptr) {
- if (plugin->file_decode != nullptr) {
- dc.Lock();
-
- if (decoder_file_decode(*plugin, decoder, path_fs))
- return true;
-
- dc.Unlock();
- } else if (plugin->stream_decode != nullptr) {
- InputStream *input_stream;
- bool success;
-
- input_stream = decoder_input_stream_open(dc, path_fs);
- if (input_stream == nullptr)
- continue;
-
- dc.Lock();
-
- success = decoder_stream_decode(*plugin, decoder,
- *input_stream);
-
- dc.Unlock();
-
- input_stream->Close();
-
- if (success) {
- dc.Lock();
- return true;
- }
- }
- }
-
- dc.Lock();
- return false;
-}
-
-static void
-decoder_run_song(DecoderControl &dc,
- const Song *song, const char *uri)
-{
- Decoder decoder(dc, dc.start_ms > 0,
- song->tag != nullptr && song->IsFile()
- ? new Tag(*song->tag) : nullptr);
- int ret;
-
- dc.state = DecoderState::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 != nullptr)
- decoder_flush_chunk(decoder);
-
- dc.Lock();
-
- if (ret)
- dc.state = DecoderState::STOP;
- else {
- dc.state = DecoderState::ERROR;
-
- const char *error_uri = song->uri;
- const std::string allocated = uri_remove_auth(error_uri);
- if (!allocated.empty())
- error_uri = allocated.c_str();
-
- dc.error.Format(decoder_domain,
- "Failed to decode %s", error_uri);
- }
-
- dc.client_cond.signal();
-}
-
-static void
-decoder_run(DecoderControl &dc)
-{
- dc.ClearError();
-
- const Song *song = dc.song;
- assert(song != nullptr);
-
- const std::string uri = song->IsFile()
- ? std::string(map_song_fs(*song).c_str())
- : song->GetURI();
-
- if (uri.empty()) {
- dc.state = DecoderState::ERROR;
- dc.error.Set(decoder_domain, "Failed to map song");
-
- decoder_command_finished_locked(dc);
- return;
- }
-
- decoder_run_song(dc, song, uri.c_str());
-
-}
-
-static void
-decoder_task(void *arg)
-{
- DecoderControl &dc = *(DecoderControl *)arg;
-
- dc.Lock();
-
- do {
- assert(dc.state == DecoderState::STOP ||
- dc.state == DecoderState::ERROR);
-
- switch (dc.command) {
- case DecoderCommand::START:
- dc.CycleMixRamp();
- dc.replay_gain_prev_db = dc.replay_gain_db;
- dc.replay_gain_db = 0;
-
- decoder_run(dc);
- break;
-
- case DecoderCommand::SEEK:
- /* this seek was too late, and the decoder had
- already finished; start a new decoder */
-
- /* we need to clear the pipe here; usually the
- PlayerThread is responsible, but it is not
- aware that the decoder has finished */
- dc.pipe->Clear(*dc.buffer);
-
- decoder_run(dc);
- break;
-
- case DecoderCommand::STOP:
- decoder_command_finished_locked(dc);
- break;
-
- case DecoderCommand::NONE:
- dc.Wait();
- break;
- }
- } while (dc.command != DecoderCommand::NONE || !dc.quit);
-
- dc.Unlock();
-}
-
-void
-decoder_thread_start(DecoderControl &dc)
-{
- assert(!dc.thread.IsDefined());
-
- dc.quit = false;
-
- Error error;
- if (!dc.thread.Start(decoder_task, &dc, error))
- FatalError(error);
-}
diff --git a/src/DecoderThread.hxx b/src/DecoderThread.hxx
deleted file mode 100644
index a2d533f93..000000000
--- a/src/DecoderThread.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_THREAD_HXX
-#define MPD_DECODER_THREAD_HXX
-
-struct DecoderControl;
-
-void
-decoder_thread_start(DecoderControl &dc);
-
-#endif
diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx
deleted file mode 100644
index e91587a7f..000000000
--- a/src/DespotifyUtils.cxx
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "DespotifyUtils.hxx"
-#include "tag/Tag.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-extern "C" {
-#include <despotify.h>
-}
-
-#include <stdio.h>
-
-const Domain despotify_domain("despotify");
-
-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, gcc_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] = nullptr;
- }
- }
-}
-
-
-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;
-
- snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber);
- snprintf(date, sizeof(date), "%d", track->year);
- snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
- track->file_bitrate / 1000,
- track->geo_restricted ? "" : "not ");
- tag->AddItem(TAG_TITLE, track->title);
- tag->AddItem(TAG_ARTIST, track->artist->name);
- tag->AddItem(TAG_TRACK, tracknum);
- tag->AddItem(TAG_ALBUM, track->album);
- tag->AddItem(TAG_DATE, date);
- tag->AddItem(TAG_COMMENT, comment);
- tag->time = track->length / 1000;
-
- return tag;
-}
-
-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, nullptr);
- passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
- high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
-
- if (user == nullptr || passwd == nullptr) {
- LogDebug(despotify_domain,
- "disabling despotify because account is not configured");
- return nullptr;
- }
-
- if (!despotify_init()) {
- LogWarning(despotify_domain, "Can't initialize despotify");
- return nullptr;
- }
-
- g_session = despotify_init_client(callback, nullptr,
- high_bitrate, true);
- if (!g_session) {
- LogWarning(despotify_domain,
- "Can't initialize despotify client");
- return nullptr;
- }
-
- if (!despotify_authenticate(g_session, user, passwd)) {
- LogWarning(despotify_domain,
- "Can't authenticate despotify session");
- despotify_exit(g_session);
- return nullptr;
- }
-
- return g_session;
-}
diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx
deleted file mode 100644
index c0d4af47c..000000000
--- a/src/DespotifyUtils.hxx
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DESPOTIFY_H
-#define MPD_DESPOTIFY_H
-
-struct Tag;
-struct despotify_session;
-struct ds_track;
-
-extern const class Domain despotify_domain;
-
-/**
- * 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 nullptr 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/DetachedSong.cxx b/src/DetachedSong.cxx
new file mode 100644
index 000000000..906e29bba
--- /dev/null
+++ b/src/DetachedSong.cxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DetachedSong.hxx"
+#include "db/LightSong.hxx"
+#include "util/UriUtil.hxx"
+#include "fs/Traits.hxx"
+
+DetachedSong::DetachedSong(const LightSong &other)
+ :uri(other.GetURI().c_str()),
+ real_uri(other.real_uri != nullptr ? other.real_uri : ""),
+ tag(*other.tag),
+ mtime(other.mtime),
+ start_time(other.start_time),
+ end_time(other.end_time) {}
+
+DetachedSong::~DetachedSong()
+{
+ /* this destructor exists here just so it won't inlined */
+}
+
+bool
+DetachedSong::IsRemote() const
+{
+ return uri_has_scheme(GetRealURI());
+}
+
+bool
+DetachedSong::IsAbsoluteFile() const
+{
+ return PathTraitsUTF8::IsAbsolute(GetRealURI());
+}
+
+bool
+DetachedSong::IsInDatabase() const
+{
+ /* here, we use GetURI() and not GetRealURI() because
+ GetRealURI() is never relative */
+
+ const char *_uri = GetURI();
+ return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri);
+}
+
+SignedSongTime
+DetachedSong::GetDuration() const
+{
+ SongTime a = start_time, b = end_time;
+ if (!b.IsPositive()) {
+ if (tag.duration.IsNegative())
+ return tag.duration;
+
+ b = SongTime(tag.duration);
+ }
+
+ return SignedSongTime(b - a);
+}
diff --git a/src/DetachedSong.hxx b/src/DetachedSong.hxx
new file mode 100644
index 000000000..021b5de29
--- /dev/null
+++ b/src/DetachedSong.hxx
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DETACHED_SONG_HXX
+#define MPD_DETACHED_SONG_HXX
+
+#include "check.h"
+#include "tag/Tag.hxx"
+#include "Chrono.hxx"
+#include "Compiler.h"
+
+#include <string>
+#include <utility>
+
+#include <time.h>
+
+struct LightSong;
+class Storage;
+
+class DetachedSong {
+ friend DetachedSong map_song_detach(const LightSong &song);
+ friend DetachedSong DatabaseDetachSong(const Storage &db,
+ const LightSong &song);
+
+ /**
+ * An UTF-8-encoded URI referring to the song file. This can
+ * be one of:
+ *
+ * - an absolute URL with a scheme
+ * (e.g. "http://example.com/foo.mp3")
+ *
+ * - an absolute file name
+ *
+ * - a file name relative to the music directory
+ */
+ std::string uri;
+
+ /**
+ * The "real" URI, the one to be used for opening the
+ * resource. If this attribute is empty, then #uri shall be
+ * used.
+ *
+ * This attribute is used for songs from the database which
+ * have a relative URI.
+ */
+ std::string real_uri;
+
+ Tag tag;
+
+ time_t mtime;
+
+ /**
+ * Start of this sub-song within the file.
+ */
+ SongTime start_time;
+
+ /**
+ * End of this sub-song within the file.
+ * Unused if zero.
+ */
+ SongTime end_time;
+
+ explicit DetachedSong(const LightSong &other);
+
+public:
+ explicit DetachedSong(const DetachedSong &) = default;
+
+ explicit DetachedSong(const char *_uri)
+ :uri(_uri),
+ mtime(0),
+ start_time(SongTime::zero()), end_time(SongTime::zero()) {}
+
+ explicit DetachedSong(const std::string &_uri)
+ :uri(_uri),
+ mtime(0),
+ start_time(SongTime::zero()), end_time(SongTime::zero()) {}
+
+ explicit DetachedSong(std::string &&_uri)
+ :uri(std::move(_uri)),
+ mtime(0),
+ start_time(SongTime::zero()), end_time(SongTime::zero()) {}
+
+ template<typename U>
+ DetachedSong(U &&_uri, Tag &&_tag)
+ :uri(std::forward<U>(_uri)),
+ tag(std::move(_tag)),
+ mtime(0),
+ start_time(SongTime::zero()), end_time(SongTime::zero()) {}
+
+ DetachedSong(DetachedSong &&) = default;
+
+ ~DetachedSong();
+
+ gcc_pure
+ const char *GetURI() const {
+ return uri.c_str();
+ }
+
+ template<typename T>
+ void SetURI(T &&_uri) {
+ uri = std::forward<T>(_uri);
+ }
+
+ /**
+ * Does this object have a "real" URI different from the
+ * displayed URI?
+ */
+ gcc_pure
+ bool HasRealURI() const {
+ return !real_uri.empty();
+ }
+
+ /**
+ * Returns "real" URI (#real_uri) and falls back to just
+ * GetURI().
+ */
+ gcc_pure
+ const char *GetRealURI() const {
+ return (HasRealURI() ? real_uri : uri).c_str();
+ }
+
+ template<typename T>
+ void SetRealURI(T &&_uri) {
+ real_uri = std::forward<T>(_uri);
+ }
+
+ /**
+ * Returns true if both objects refer to the same physical
+ * song.
+ */
+ gcc_pure
+ bool IsSame(const DetachedSong &other) const {
+ return uri == other.uri;
+ }
+
+ gcc_pure gcc_nonnull_all
+ bool IsURI(const char *other_uri) const {
+ return uri == other_uri;
+ }
+
+ gcc_pure
+ bool IsRemote() const;
+
+ gcc_pure
+ bool IsFile() const {
+ return !IsRemote();
+ }
+
+ gcc_pure
+ bool IsAbsoluteFile() const;
+
+ gcc_pure
+ bool IsInDatabase() const;
+
+ const Tag &GetTag() const {
+ return tag;
+ }
+
+ Tag &WritableTag() {
+ return tag;
+ }
+
+ void SetTag(const Tag &_tag) {
+ tag = Tag(_tag);
+ }
+
+ void SetTag(Tag &&_tag) {
+ tag = std::move(_tag);
+ }
+
+ void MoveTagFrom(DetachedSong &&other) {
+ tag = std::move(other.tag);
+ }
+
+ time_t GetLastModified() const {
+ return mtime;
+ }
+
+ void SetLastModified(time_t _value) {
+ mtime = _value;
+ }
+
+ SongTime GetStartTime() const {
+ return start_time;
+ }
+
+ void SetStartTime(SongTime _value) {
+ start_time = _value;
+ }
+
+ SongTime GetEndTime() const {
+ return end_time;
+ }
+
+ void SetEndTime(SongTime _value) {
+ end_time = _value;
+ }
+
+ gcc_pure
+ SignedSongTime GetDuration() const;
+
+ /**
+ * Update the #tag and #mtime.
+ *
+ * @return true on success
+ */
+ bool Update();
+};
+
+#endif
diff --git a/src/Directory.cxx b/src/Directory.cxx
deleted file mode 100644
index 238bd149c..000000000
--- a/src/Directory.cxx
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Directory.hxx"
-#include "SongFilter.hxx"
-#include "PlaylistVector.hxx"
-#include "DatabaseLock.hxx"
-#include "SongSort.hxx"
-#include "Song.hxx"
-#include "fs/Traits.hxx"
-#include "util/Error.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)
-{
-#if !CLANG_CHECK_VERSION(3,6)
- /* disabled on clang due to -Wtautological-pointer-compare */
- assert(path != nullptr);
-#endif
-
- 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 != nullptr);
- assert((*path == 0) == (parent == nullptr));
-
- 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());
-
- return PathTraits::GetBaseUTF8(path);
-}
-
-Directory *
-Directory::CreateChild(const char *name_utf8)
-{
- assert(holding_db_lock());
- assert(name_utf8 != nullptr);
- assert(*name_utf8 != 0);
-
- char *allocated;
- const char *path_utf8;
- if (IsRoot()) {
- allocated = nullptr;
- path_utf8 = name_utf8;
- } else {
- allocated = g_strconcat(GetPath(),
- "/", name_utf8, nullptr);
- 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 nullptr;
-}
-
-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 != nullptr);
-
- 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 = nullptr;
- break;
- }
-
- if (slash != nullptr)
- *slash = '\0';
-
- d = d->FindChild(name);
- if (d == nullptr || slash == nullptr)
- break;
-
- name = slash + 1;
- }
-
- g_free(duplicated);
-
- return d;
-}
-
-void
-Directory::AddSong(Song *song)
-{
- assert(holding_db_lock());
- assert(song != nullptr);
- assert(song->parent == this);
-
- list_add_tail(&song->siblings, &songs);
-}
-
-void
-Directory::RemoveSong(Song *song)
-{
- assert(holding_db_lock());
- assert(song != nullptr);
- assert(song->parent == this);
-
- list_del(&song->siblings);
-}
-
-const Song *
-Directory::FindSong(const char *name_utf8) const
-{
- assert(holding_db_lock());
- assert(name_utf8 != nullptr);
-
- Song *song;
- directory_for_each_song(song, *this) {
- assert(song->parent == this);
-
- if (strcmp(song->uri, name_utf8) == 0)
- return song;
- }
-
- return nullptr;
-}
-
-Song *
-Directory::LookupSong(const char *uri)
-{
- char *duplicated, *base;
-
- assert(holding_db_lock());
- assert(uri != nullptr);
-
- duplicated = g_strdup(uri);
- base = strrchr(duplicated, '/');
-
- Directory *d = this;
- if (base != nullptr) {
- *base++ = 0;
- d = d->LookupDirectory(duplicated);
- if (d == nullptr) {
- g_free(duplicated);
- return nullptr;
- }
- } else
- base = duplicated;
-
- Song *song = d->FindSong(base);
- assert(song == nullptr || song->parent == d);
-
- g_free(duplicated);
- return song;
-
-}
-
-static int
-directory_cmp(gcc_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(nullptr, &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,
- Error &error) const
-{
- assert(!error.IsDefined());
-
- if (visit_song) {
- Song *song;
- directory_for_each_song(song, *this)
- if ((filter == nullptr || filter->Match(*song)) &&
- !visit_song(*song, error))
- return false;
- }
-
- if (visit_playlist) {
- for (const PlaylistInfo &p : playlists)
- if (!visit_playlist(p, *this, error))
- return false;
- }
-
- Directory *child;
- directory_for_each_child(child, *this) {
- if (visit_directory &&
- !visit_directory(*child, error))
- return false;
-
- if (recursive &&
- !child->Walk(recursive, filter,
- visit_directory, visit_song, visit_playlist,
- error))
- return false;
- }
-
- return true;
-}
diff --git a/src/Directory.hxx b/src/Directory.hxx
deleted file mode 100644
index 380a6b790..000000000
--- a/src/Directory.hxx
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DIRECTORY_HXX
-#define MPD_DIRECTORY_HXX
-
-#include "check.h"
-#include "util/list.h"
-#include "Compiler.h"
-#include "DatabaseVisitor.hxx"
-#include "PlaylistVector.hxx"
-
-#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;
-class Error;
-
-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 nullptr 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 == nullptr;
- }
-
- /**
- * 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 nullptr 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,
- Error &error) 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
deleted file mode 100644
index fa330d126..000000000
--- a/src/DirectorySave.cxx
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "DirectorySave.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
-#include "SongSave.hxx"
-#include "PlaylistDatabase.hxx"
-#include "TextFile.hxx"
-#include "util/NumberParser.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-#define DIRECTORY_DIR "directory: "
-#define DIRECTORY_MTIME "mtime: "
-#define DIRECTORY_BEGIN "begin: "
-#define DIRECTORY_END "end: "
-
-static constexpr Domain directory_domain("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) {
- fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName());
-
- 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,
- Error &error)
-{
- bool success;
-
- if (parent.FindChild(name) != nullptr) {
- error.Format(directory_domain,
- "Duplicate subdirectory '%s'", name);
- return nullptr;
- }
-
- Directory *directory = parent.CreateChild(name);
-
- const char *line = file.ReadLine();
- if (line == nullptr) {
- error.Set(directory_domain, "Unexpected end of file");
- directory->Delete();
- return nullptr;
- }
-
- if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
- directory->mtime =
- ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1);
-
- line = file.ReadLine();
- if (line == nullptr) {
- error.Set(directory_domain, "Unexpected end of file");
- directory->Delete();
- return nullptr;
- }
- }
-
- if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
- error.Format(directory_domain, "Malformed line: %s", line);
- directory->Delete();
- return nullptr;
- }
-
- success = directory_load(file, *directory, error);
- if (!success) {
- directory->Delete();
- return nullptr;
- }
-
- return directory;
-}
-
-bool
-directory_load(TextFile &file, Directory &directory, Error &error)
-{
- const char *line;
-
- while ((line = file.ReadLine()) != nullptr &&
- !g_str_has_prefix(line, DIRECTORY_END)) {
- if (g_str_has_prefix(line, DIRECTORY_DIR)) {
- Directory *subdir =
- directory_load_subdir(file, directory,
- line + sizeof(DIRECTORY_DIR) - 1,
- error);
- if (subdir == nullptr)
- return false;
- } else if (g_str_has_prefix(line, SONG_BEGIN)) {
- const char *name = line + sizeof(SONG_BEGIN) - 1;
- Song *song;
-
- if (directory.FindSong(name) != nullptr) {
- error.Format(directory_domain,
- "Duplicate song '%s'", name);
- return false;
- }
-
- song = song_load(file, &directory, name, error);
- if (song == nullptr)
- return false;
-
- directory.AddSong(song);
- } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) {
- /* duplicate the name, because
- playlist_metadata_load() will overwrite the
- buffer */
- char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1);
-
- if (!playlist_metadata_load(file, directory.playlists,
- name, error)) {
- g_free(name);
- return false;
- }
-
- g_free(name);
- } else {
- error.Format(directory_domain,
- "Malformed line: %s", line);
- return false;
- }
- }
-
- return true;
-}
diff --git a/src/DirectorySave.hxx b/src/DirectorySave.hxx
deleted file mode 100644
index 02814490a..000000000
--- a/src/DirectorySave.hxx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DIRECTORY_SAVE_HXX
-#define MPD_DIRECTORY_SAVE_HXX
-
-#include <stdio.h>
-
-struct Directory;
-class TextFile;
-class Error;
-
-void
-directory_save(FILE *fp, const Directory &directory);
-
-bool
-directory_load(TextFile &file, Directory &directory, Error &error);
-
-#endif
diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx
deleted file mode 100644
index b3397f25c..000000000
--- a/src/EncoderAPI.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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/Tag.hxx"
-#include "ConfigData.hxx"
-
-#endif
diff --git a/src/EncoderList.cxx b/src/EncoderList.cxx
deleted file mode 100644
index 7760a9582..000000000
--- a/src/EncoderList.cxx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
- nullptr
-};
-
-const EncoderPlugin *
-encoder_plugin_get(const char *name)
-{
- encoder_plugins_for_each(plugin)
- if (strcmp(plugin->name, name) == 0)
- return plugin;
-
- return nullptr;
-}
diff --git a/src/EncoderList.hxx b/src/EncoderList.hxx
deleted file mode 100644
index 8cbb389a4..000000000
--- a/src/EncoderList.hxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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) != nullptr; \
- ++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 nullptr if none
- * was found
- */
-const EncoderPlugin *
-encoder_plugin_get(const char *name);
-
-#endif
diff --git a/src/EncoderPlugin.hxx b/src/EncoderPlugin.hxx
deleted file mode 100644
index 8ad5ea0c6..000000000
--- a/src/EncoderPlugin.hxx
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ENCODER_PLUGIN_HXX
-#define MPD_ENCODER_PLUGIN_HXX
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stddef.h>
-
-struct EncoderPlugin;
-struct AudioFormat;
-struct config_param;
-struct Tag;
-class Error;
-
-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,
- Error &error);
-
- void (*finish)(Encoder *encoder);
-
- bool (*open)(Encoder *encoder,
- AudioFormat &audio_format,
- Error &error);
-
- void (*close)(Encoder *encoder);
-
- bool (*end)(Encoder *encoder, Error &error);
-
- bool (*flush)(Encoder *encoder, Error &error);
-
- bool (*pre_tag)(Encoder *encoder, Error &error);
-
- bool (*tag)(Encoder *encoder, const Tag *tag,
- Error &error);
-
- bool (*write)(Encoder *encoder,
- const void *data, size_t length,
- Error &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 nullptr to ignore errors.
- * @return an encoder object on success, nullptr on failure
- */
-static inline Encoder *
-encoder_init(const EncoderPlugin &plugin, const config_param &param,
- Error &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
- * @return true on success
- */
-static inline bool
-encoder_open(Encoder *encoder, AudioFormat &audio_format,
- Error &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 != nullptr)
- 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
- * @return true on success
- */
-static inline bool
-encoder_end(Encoder *encoder, Error &error)
-{
- assert(encoder->open);
- assert(!encoder->end);
-
-#ifndef NDEBUG
- encoder->end = true;
-#endif
-
- /* this method is optional */
- return encoder->plugin.end != nullptr
- ? 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
- * @return true on success
- */
-static inline bool
-encoder_flush(Encoder *encoder, Error &error)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag);
- assert(!encoder->tag);
- assert(!encoder->end);
-
- /* this method is optional */
- return encoder->plugin.flush != nullptr
- ? 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
- * @return true on success
- */
-static inline bool
-encoder_pre_tag(Encoder *encoder, Error &error)
-{
- assert(encoder->open);
- assert(!encoder->pre_tag);
- assert(!encoder->tag);
- assert(!encoder->end);
-
- /* this method is optional */
- bool success = encoder->plugin.pre_tag != nullptr
- ? 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
- * @return true on success
- */
-static inline bool
-encoder_tag(Encoder *encoder, const Tag *tag, Error &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 != nullptr
- ? 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
- * @return true on success
- */
-static inline bool
-encoder_write(Encoder *encoder, const void *data, size_t length,
- Error &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, nullptr on failure
- */
-static inline const char *
-encoder_get_mime_type(Encoder *encoder)
-{
- /* this method is optional */
- return encoder->plugin.get_mime_type != nullptr
- ? encoder->plugin.get_mime_type(encoder)
- : nullptr;
-}
-
-#endif
diff --git a/src/ExcludeList.cxx b/src/ExcludeList.cxx
deleted file mode 100644
index d34afd897..000000000
--- a/src/ExcludeList.cxx
+++ /dev/null
@@ -1,83 +0,0 @@
-
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * The .mpdignore backend code.
- *
- */
-
-#include "config.h"
-#include "ExcludeList.hxx"
-#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-#include <errno.h>
-
-static constexpr Domain exclude_list_domain("exclude_list");
-
-bool
-ExcludeList::LoadFile(Path path_fs)
-{
- FILE *file = FOpen(path_fs, FOpenMode::ReadText);
- if (file == nullptr) {
- const int e = errno;
- if (e != ENOENT) {
- const auto path_utf8 = path_fs.ToUTF8();
- FormatErrno(exclude_list_domain,
- "Failed to open %s",
- path_utf8.c_str());
- }
-
- return false;
- }
-
- char line[1024];
- while (fgets(line, sizeof(line), file) != nullptr) {
- char *p = strchr(line, '#');
- if (p != nullptr)
- *p = 0;
-
- p = g_strstrip(line);
- if (*p != 0)
- patterns.emplace_front(p);
- }
-
- fclose(file);
-
- return true;
-}
-
-bool
-ExcludeList::Check(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
deleted file mode 100644
index e15f902f9..000000000
--- a/src/ExcludeList.hxx
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * The .mpdignore backend code.
- *
- */
-
-#ifndef MPD_EXCLUDE_H
-#define MPD_EXCLUDE_H
-
-#include "Compiler.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(Path path_fs);
-
- /**
- * Checks whether one of the patterns in the .mpdignore file matches
- * the specified file name.
- */
- bool Check(Path name_fs) const;
-};
-
-
-#endif
diff --git a/src/FilterConfig.cxx b/src/FilterConfig.cxx
deleted file mode 100644
index cfac1c756..000000000
--- a/src/FilterConfig.cxx
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FilterConfig.hxx"
-#include "filter/ChainFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "ConfigData.hxx"
-#include "ConfigOption.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-
-#include <algorithm>
-
-#include <string.h>
-
-/**
- * Find the "filter" configuration block for the specified name.
- *
- * @param filter_template_name the name of the filter template
- * @param error space to return an error description
- * @return the configuration block, or nullptr if none was configured
- */
-static const struct config_param *
-filter_plugin_config(const char *filter_template_name, Error &error)
-{
- const struct config_param *param = nullptr;
-
- while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != nullptr) {
- const char *name = param->GetBlockValue("name");
- if (name == nullptr) {
- error.Format(config_domain,
- "filter configuration without 'name' name in line %d",
- param->line);
- return nullptr;
- }
-
- if (strcmp(name, filter_template_name) == 0)
- return param;
- }
-
- error.Format(config_domain,
- "filter template not found: %s",
- filter_template_name);
- return nullptr;
-}
-
-static bool
-filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
-{
- const struct config_param *cfg =
- filter_plugin_config(template_name, error);
- if (cfg == nullptr)
- // The error has already been set, just stop.
- return false;
-
- // Instantiate one of those filter plugins with the template name as a hint
- Filter *f = filter_configured_new(*cfg, error);
- if (f == nullptr)
- // The error has already been set, just stop.
- return false;
-
- const char *plugin_name = cfg->GetBlockValue("plugin",
- "unknown");
- filter_chain_append(chain, plugin_name, f);
-
- return true;
-}
-
-bool
-filter_chain_parse(Filter &chain, const char *spec, Error &error)
-{
- const char *const end = spec + strlen(spec);
-
- while (true) {
- const char *comma = std::find(spec, end, ',');
- if (comma > spec) {
- const std::string name(spec, comma);
- if (!filter_chain_append_new(chain, name.c_str(),
- error))
- return false;
- }
-
- if (comma == end)
- break;
-
- spec = comma + 1;
- }
-
- return true;
-}
diff --git a/src/FilterConfig.hxx b/src/FilterConfig.hxx
deleted file mode 100644
index 6b074b930..000000000
--- a/src/FilterConfig.hxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Utility functions for filter configuration
- */
-
-#ifndef MPD_FILTER_CONFIG_HXX
-#define MPD_FILTER_CONFIG_HXX
-
-class Filter;
-class Error;
-
-/**
- * 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 true on success
- */
-bool
-filter_chain_parse(Filter &chain, const char *spec, Error &error);
-
-#endif
diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx
deleted file mode 100644
index e2884e82f..000000000
--- a/src/FilterInternal.hxx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Internal stuff for the filter core and filter plugins.
- */
-
-#ifndef MPD_FILTER_INTERNAL_HXX
-#define MPD_FILTER_INTERNAL_HXX
-
-#include <stddef.h>
-
-struct AudioFormat;
-class Error;
-
-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 nullptr
- * to ignore errors.
- * @return the format of outgoing data or
- * AudioFormat::Undefined() on error
- */
- virtual AudioFormat Open(AudioFormat &af, Error &error) = 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 nullptr
- * to ignore errors.
- * @return the destination buffer on success (will be
- * invalidated by filter_close() or filter_filter()), nullptr on
- * error
- */
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r,
- Error &error) = 0;
-};
-
-#endif
diff --git a/src/FilterPlugin.cxx b/src/FilterPlugin.cxx
deleted file mode 100644
index 608542f92..000000000
--- a/src/FilterPlugin.cxx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "ConfigData.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-
-#include <assert.h>
-
-Filter *
-filter_new(const struct filter_plugin *plugin,
- const config_param &param, Error &error)
-{
- assert(plugin != nullptr);
- assert(!error.IsDefined());
-
- return plugin->init(param, error);
-}
-
-Filter *
-filter_configured_new(const config_param &param, Error &error)
-{
- assert(!error.IsDefined());
-
- const char *plugin_name = param.GetBlockValue("plugin");
- if (plugin_name == nullptr) {
- error.Set(config_domain, "No filter plugin specified");
- return nullptr;
- }
-
- const filter_plugin *plugin = filter_plugin_by_name(plugin_name);
- if (plugin == nullptr) {
- error.Format(config_domain,
- "No such filter plugin: %s", plugin_name);
- return nullptr;
- }
-
- return filter_new(plugin, param, error);
-}
diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx
deleted file mode 100644
index 5ff4637b3..000000000
--- a/src/FilterPlugin.hxx
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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
-
-struct config_param;
-class Filter;
-class Error;
-
-struct filter_plugin {
- const char *name;
-
- /**
- * Allocates and configures a filter.
- */
- Filter *(*init)(const config_param &param, Error &error);
-};
-
-/**
- * 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 nullptr to
- * ignore errors.
- * @return a new filter object, or nullptr on error
- */
-Filter *
-filter_new(const struct filter_plugin *plugin,
- const config_param &param, Error &error);
-
-/**
- * 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 nullptr to
- * ignore errors.
- * @return a new filter object, or nullptr on error
- */
-Filter *
-filter_configured_new(const config_param &param, Error &error);
-
-#endif
diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx
deleted file mode 100644
index b3b08505e..000000000
--- a/src/FilterRegistry.cxx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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,
- nullptr,
-};
-
-const struct filter_plugin *
-filter_plugin_by_name(const char *name)
-{
- for (unsigned i = 0; filter_plugins[i] != nullptr; ++i)
- if (strcmp(filter_plugins[i]->name, name) == 0)
- return filter_plugins[i];
-
- return nullptr;
-}
diff --git a/src/FilterRegistry.hxx b/src/FilterRegistry.hxx
deleted file mode 100644
index 286420f92..000000000
--- a/src/FilterRegistry.hxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * This library manages all filter plugins which are enabled at
- * compile time.
- */
-
-#ifndef MPD_FILTER_REGISTRY_HXX
-#define MPD_FILTER_REGISTRY_HXX
-
-#include "Compiler.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;
-
-gcc_pure
-const struct filter_plugin *
-filter_plugin_by_name(const char *name);
-
-#endif
diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx
index 86bfb3e2a..9c60f6357 100644
--- a/src/GlobalEvents.cxx
+++ b/src/GlobalEvents.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,6 @@
#include "GlobalEvents.hxx"
#include "util/Manual.hxx"
#include "event/DeferredMonitor.hxx"
-#include "Compiler.h"
#include <atomic>
diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx
index 47ec6d070..a9df03724 100644
--- a/src/GlobalEvents.hxx
+++ b/src/GlobalEvents.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -33,12 +33,6 @@ class EventLoop;
namespace GlobalEvents {
enum Event {
- /** database update was finished */
- UPDATE,
-
- /** during database update, a song was deleted */
- DELETE,
-
/** an idle event was emitted */
IDLE,
@@ -48,9 +42,6 @@ namespace GlobalEvents {
/** the current song's tag has changed */
TAG,
- /** a hardware mixer plugin has detected a change */
- MIXER,
-
#ifdef WIN32
/** shutdown requested */
SHUTDOWN,
diff --git a/src/IOThread.cxx b/src/IOThread.cxx
index a14f12eb1..e21ede4f3 100644
--- a/src/IOThread.cxx
+++ b/src/IOThread.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "thread/Thread.hxx"
+#include "thread/Name.hxx"
#include "event/Loop.hxx"
#include "system/FatalError.hxx"
#include "util/Error.hxx"
@@ -48,6 +49,8 @@ io_thread_run(void)
static void
io_thread_func(gcc_unused void *arg)
{
+ SetThreadName("io");
+
/* lock+unlock to synchronize with io_thread_start(), to be
sure that io.thread is set */
io.mutex.lock();
diff --git a/src/IOThread.hxx b/src/IOThread.hxx
index 3384a09e0..f6f5dffec 100644
--- a/src/IOThread.hxx
+++ b/src/IOThread.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -49,7 +49,7 @@ io_thread_quit(void);
void
io_thread_deinit(void);
-gcc_pure
+gcc_const
EventLoop &
io_thread_get();
diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx
index bfa2e8558..4c13c2c2c 100644
--- a/src/IcyMetaDataParser.cxx
+++ b/src/IcyMetaDataParser.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,10 @@
#include "config.h"
#include "IcyMetaDataParser.hxx"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
@@ -37,7 +36,7 @@ IcyMetaDataParser::Reset()
return;
if (data_rest == 0 && meta_size > 0)
- g_free(meta_data);
+ delete[] meta_data;
delete tag;
@@ -66,7 +65,7 @@ IcyMetaDataParser::Data(size_t length)
}
static void
-icy_add_item(Tag &tag, TagType type, const char *value)
+icy_add_item(TagBuilder &tag, TagType type, const char *value)
{
size_t length = strlen(value);
@@ -81,7 +80,7 @@ icy_add_item(Tag &tag, TagType type, const char *value)
}
static void
-icy_parse_tag_item(Tag &tag, const char *name, const char *value)
+icy_parse_tag_item(TagBuilder &tag, const char *name, const char *value)
{
if (strcmp(name, "StreamTitle") == 0)
icy_add_item(tag, TAG_TITLE, value);
@@ -122,7 +121,7 @@ icy_parse_tag(char *p, char *const end)
assert(end != nullptr);
assert(p <= end);
- Tag *tag = new Tag();
+ TagBuilder tag;
while (p != end) {
const char *const name = p;
@@ -153,7 +152,7 @@ icy_parse_tag(char *p, char *const end)
*quote = 0;
p = quote + 1;
- icy_parse_tag_item(*tag, name, value);
+ icy_parse_tag_item(tag, name, value);
char *semicolon = std::find(p, end, ';');
if (semicolon == end)
@@ -161,7 +160,7 @@ icy_parse_tag(char *p, char *const end)
p = semicolon + 1;
}
- return tag;
+ return tag.CommitNew();
}
size_t
@@ -190,7 +189,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
/* initialize metadata reader, allocate enough
memory (+1 for the null terminator) */
meta_position = 0;
- meta_data = (char *)g_malloc(meta_size + 1);
+ meta_data = new char[meta_size + 1];
}
assert(meta_position < meta_size);
@@ -211,7 +210,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
delete tag;
tag = icy_parse_tag(meta_data, meta_data + meta_size);
- g_free(meta_data);
+ delete[] meta_data;
/* change back to normal data mode */
@@ -221,3 +220,32 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
return length;
}
+
+size_t
+IcyMetaDataParser::ParseInPlace(void *data, size_t length)
+{
+ uint8_t *const dest0 = (uint8_t *)data;
+ uint8_t *dest = dest0;
+ const uint8_t *src = dest0;
+
+ while (length > 0) {
+ size_t chunk = Data(length);
+ if (chunk > 0) {
+ memmove(dest, src, chunk);
+ dest += chunk;
+ src += chunk;
+ length -= chunk;
+
+ if (length == 0)
+ break;
+ }
+
+ chunk = Meta(src, length);
+ if (chunk > 0) {
+ src += chunk;
+ length -= chunk;
+ }
+ }
+
+ return dest - dest0;
+}
diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx
index 6bcb09668..3075485b2 100644
--- a/src/IcyMetaDataParser.hxx
+++ b/src/IcyMetaDataParser.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -75,6 +75,13 @@ public:
*/
size_t Meta(const void *data, size_t length);
+ /**
+ * Parse data and eliminate metadata.
+ *
+ * @return the number of data bytes remaining in the buffer
+ */
+ size_t ParseInPlace(void *data, size_t length);
+
Tag *ReadTag() {
Tag *result = tag;
tag = nullptr;
diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx
deleted file mode 100644
index f5e981c2f..000000000
--- a/src/IcyMetaDataServer.cxx
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "IcyMetaDataServer.hxx"
-#include "Page.hxx"
-#include "tag/Tag.hxx"
-#include "util/FormatString.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-char*
-icy_server_metadata_header(const char *name,
- const char *genre, const char *url,
- const char *content_type, int metaint)
-{
- return FormatNew("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 = FormatNew("nStreamTitle='%s';"
- "StreamUrl='%s';",
- stream_title,
- stream_url);
-
- 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) {
- delete[] icy_metadata;
- return nullptr;
- }
-
- return icy_metadata;
-}
-
-Page *
-icy_server_metadata_page(const Tag &tag, const TagType *types)
-{
- const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES];
- gint last_item, item;
- guint position;
- gchar *icy_string;
- gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
- // "StreamTitle='';StreamUrl='';"
- // = 4081 - 28
- stream_title[0] = '\0';
-
- last_item = -1;
-
- 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 == nullptr)
- return nullptr;
-
- Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1);
-
- delete[] icy_string;
-
- return icy_metadata;
-}
diff --git a/src/IcyMetaDataServer.hxx b/src/IcyMetaDataServer.hxx
deleted file mode 100644
index 20964ce74..000000000
--- a/src/IcyMetaDataServer.hxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ICY_META_DATA_SERVER_HXX
-#define MPD_ICY_META_DATA_SERVER_HXX
-
-#include "tag/TagType.h"
-
-struct Tag;
-class Page;
-
-/**
- * Free the return value with delete[].
- */
-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 TagType *types);
-
-#endif
diff --git a/src/IdTable.hxx b/src/IdTable.hxx
deleted file mode 100644
index ab021e48f..000000000
--- a/src/IdTable.hxx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ID_TABLE_HXX
-#define MPD_ID_TABLE_HXX
-
-#include "Compiler.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_n(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
index 840a5d4e5..8fe672200 100644
--- a/src/Idle.cxx
+++ b/src/Idle.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#include "config.h"
#include "Idle.hxx"
#include "GlobalEvents.hxx"
+#include "util/ASCII.hxx"
#include <atomic>
@@ -44,6 +45,8 @@ static const char *const idle_names[] = {
"update",
"subscription",
"message",
+ "neighbor",
+ "mount",
nullptr
};
@@ -69,3 +72,15 @@ idle_get_names(void)
{
return idle_names;
}
+
+unsigned
+idle_parse_name(const char *name)
+{
+ assert(name != nullptr);
+
+ for (unsigned i = 0; idle_names[i] != nullptr; ++i)
+ if (StringEqualsCaseASCII(name, idle_names[i]))
+ return 1 << i;
+
+ return 0;
+}
diff --git a/src/Idle.hxx b/src/Idle.hxx
index e5a39f403..fb7150f98 100644
--- a/src/Idle.hxx
+++ b/src/Idle.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,8 @@
#ifndef MPD_IDLE_HXX
#define MPD_IDLE_HXX
+#include "Compiler.h"
+
/** song database has been updated*/
static constexpr unsigned IDLE_DATABASE = 0x1;
@@ -59,6 +61,12 @@ static constexpr unsigned IDLE_SUBSCRIPTION = 0x200;
/** a message on the subscribed channel was received */
static constexpr unsigned IDLE_MESSAGE = 0x400;
+/** a neighbor was found or lost */
+static constexpr unsigned IDLE_NEIGHBOR = 0x800;
+
+/** the mount list has changed */
+static constexpr unsigned IDLE_MOUNT = 0x1000;
+
/**
* Adds idle flag (with bitwise "or") and queues notifications to all
* clients.
@@ -78,4 +86,12 @@ idle_get(void);
const char*const*
idle_get_names(void);
+/**
+ * Parse an idle name and return its mask. Returns 0 if the given
+ * name is unknown.
+ */
+gcc_nonnull_all gcc_pure
+unsigned
+idle_parse_name(const char *name);
+
#endif
diff --git a/src/InotifyDomain.cxx b/src/InotifyDomain.cxx
deleted file mode 100644
index 1b8e62d38..000000000
--- a/src/InotifyDomain.cxx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "InotifyDomain.hxx"
-#include "util/Domain.hxx"
-
-const Domain inotify_domain("inotify");
diff --git a/src/InotifyDomain.hxx b/src/InotifyDomain.hxx
deleted file mode 100644
index 005487804..000000000
--- a/src/InotifyDomain.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INOTIFY_DOMAIN_HXX
-#define MPD_INOTIFY_DOMAIN_HXX
-
-extern const class Domain inotify_domain;
-
-#endif
diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx
deleted file mode 100644
index c24816241..000000000
--- a/src/InotifyQueue.cxx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "InotifyQueue.hxx"
-#include "InotifyDomain.hxx"
-#include "UpdateGlue.hxx"
-#include "event/Loop.hxx"
-#include "Log.hxx"
-
-#include <string.h>
-
-/**
- * Wait this long after the last change before calling
- * update_enqueue(). This increases the probability that updates can
- * be bundled.
- */
-static constexpr unsigned 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;
- }
-
- FormatDebug(inotify_domain, "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
deleted file mode 100644
index ce79748a0..000000000
--- a/src/InotifyQueue.hxx
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INOTIFY_QUEUE_HXX
-#define MPD_INOTIFY_QUEUE_HXX
-
-#include "event/TimeoutMonitor.hxx"
-#include "Compiler.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
deleted file mode 100644
index ccf5d3e14..000000000
--- a/src/InotifySource.cxx
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "InotifySource.hxx"
-#include "InotifyDomain.hxx"
-#include "util/Error.hxx"
-#include "system/fd_util.h"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-#include <sys/inotify.h>
-#include <unistd.h>
-#include <errno.h>
-
-bool
-InotifySource::OnSocketReady(gcc_unused unsigned flags)
-{
- const auto dest = buffer.Write();
- if (dest.IsEmpty())
- FatalError("buffer full");
-
- ssize_t nbytes = read(Get(), dest.data, dest.size);
- if (nbytes < 0)
- FatalSystemError("Failed to read from inotify");
- if (nbytes == 0)
- FatalError("end of file from inotify");
-
- buffer.Append(nbytes);
-
- while (true) {
- const char *name;
-
- auto range = buffer.Read();
- const struct inotify_event *event =
- (const struct inotify_event *)
- range.data;
- if (range.size < sizeof(*event) ||
- range.size < sizeof(*event) + event->len)
- break;
-
- if (event->len > 0 && event->name[event->len - 1] == 0)
- name = event->name;
- else
- name = nullptr;
-
- callback(event->wd, event->mask, name, callback_ctx);
- buffer.Consume(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)
-{
- ScheduleRead();
-
-}
-
-InotifySource *
-InotifySource::Create(EventLoop &loop,
- mpd_inotify_callback_t callback, void *callback_ctx,
- Error &error)
-{
- int fd = inotify_init_cloexec();
- if (fd < 0) {
- error.SetErrno("inotify_init() has failed");
- return nullptr;
- }
-
- return new InotifySource(loop, callback, callback_ctx, fd);
-}
-
-int
-InotifySource::Add(const char *path_fs, unsigned mask, Error &error)
-{
- int wd = inotify_add_watch(Get(), path_fs, mask);
- if (wd < 0)
- error.SetErrno("inotify_add_watch() has failed");
-
- return wd;
-}
-
-void
-InotifySource::Remove(unsigned wd)
-{
- int ret = inotify_rm_watch(Get(), wd);
- if (ret < 0 && errno != EINVAL)
- LogErrno(inotify_domain, "inotify_rm_watch() has failed");
-
- /* 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
deleted file mode 100644
index f6ddea966..000000000
--- a/src/InotifySource.hxx
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INOTIFY_SOURCE_HXX
-#define MPD_INOTIFY_SOURCE_HXX
-
-#include "event/SocketMonitor.hxx"
-#include "util/FifoBuffer.hxx"
-#include "Compiler.h"
-
-class Error;
-
-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;
-
- FifoBuffer<uint8_t, 4096> 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,
- Error &error);
-
- /**
- * Adds a path to the notify list.
- *
- * @return a watch descriptor or -1 on error
- */
- int Add(const char *path_fs, unsigned mask, Error &error);
-
- /**
- * 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
deleted file mode 100644
index 5a015508a..000000000
--- a/src/InotifyUpdate.cxx
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "InotifyUpdate.hxx"
-#include "InotifySource.hxx"
-#include "InotifyQueue.hxx"
-#include "InotifyDomain.hxx"
-#include "Mapper.hxx"
-#include "Main.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <string>
-#include <map>
-#include <forward_list>
-
-#include <assert.h>
-#include <sys/inotify.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <dirent.h>
-
-static constexpr unsigned IN_MASK =
-#ifdef IN_ONLYDIR
- IN_ONLYDIR|
-#endif
- IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
- |IN_MOVE|IN_MOVE_SELF;
-
-struct WatchDirectory {
- WatchDirectory *parent;
-
- AllocatedPath name;
-
- int descriptor;
-
- std::forward_list<WatchDirectory> children;
-
- template<typename N>
- WatchDirectory(WatchDirectory *_parent, N &&_name,
- int _descriptor)
- :parent(_parent), name(std::forward<N>(_name)),
- descriptor(_descriptor) {}
-
- WatchDirectory(const WatchDirectory &) = delete;
- WatchDirectory &operator=(const WatchDirectory &) = delete;
-};
-
-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 != nullptr);
-
- if (directory->parent == nullptr) {
- LogWarning(inotify_domain,
- "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 AllocatedPath
-watch_directory_get_uri_fs(const WatchDirectory *directory)
-{
- if (directory->parent == nullptr)
- return AllocatedPath::Null();
-
- const auto uri = watch_directory_get_uri_fs(directory->parent);
- if (uri.IsNull())
- return directory->name;
-
- return AllocatedPath::Build(uri, directory->name);
-}
-
-/* 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') != nullptr;
-}
-
-static void
-recursive_watch_subdirectories(WatchDirectory *directory,
- const AllocatedPath &path_fs, unsigned depth)
-{
- Error error;
- DIR *dir;
- struct dirent *ent;
-
- assert(directory != nullptr);
- assert(depth <= inotify_max_depth);
- assert(!path_fs.IsNull());
-
- ++depth;
-
- if (depth > inotify_max_depth)
- return;
-
- dir = opendir(path_fs.c_str());
- if (dir == nullptr) {
- FormatErrno(inotify_domain,
- "Failed to open directory %s", path_fs.c_str());
- return;
- }
-
- while ((ent = readdir(dir))) {
- struct stat st;
- int ret;
-
- if (skip_path(ent->d_name))
- continue;
-
- const auto child_path_fs =
- AllocatedPath::Build(path_fs, ent->d_name);
- ret = StatFile(child_path_fs, st);
- if (ret < 0) {
- FormatErrno(inotify_domain,
- "Failed to stat %s",
- child_path_fs.c_str());
- continue;
- }
-
- if (!S_ISDIR(st.st_mode))
- continue;
-
- ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK,
- error);
- if (ret < 0) {
- FormatError(error,
- "Failed to register %s",
- child_path_fs.c_str());
- error.Clear();
- continue;
- }
-
- WatchDirectory *child = tree_find_watch_directory(ret);
- if (child != nullptr)
- /* already being watched */
- continue;
-
- directory->children.emplace_front(directory,
- AllocatedPath::FromFS(ent->d_name),
- ret);
- child = &directory->children.front();
-
- tree_add_watch_directory(child);
-
- recursive_watch_subdirectories(child, child_path_fs, depth);
- }
-
- closedir(dir);
-}
-
-gcc_pure
-static unsigned
-watch_directory_depth(const WatchDirectory *d)
-{
- assert(d != nullptr);
-
- unsigned depth = 0;
- while ((d = d->parent) != nullptr)
- ++depth;
-
- return depth;
-}
-
-static void
-mpd_inotify_callback(int wd, unsigned mask,
- gcc_unused const char *name, gcc_unused void *ctx)
-{
- WatchDirectory *directory;
-
- /*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
-
- directory = tree_find_watch_directory(wd);
- if (directory == nullptr)
- return;
-
- const auto uri_fs = watch_directory_get_uri_fs(directory);
-
- if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
- 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 auto &root = mapper_get_music_directory_fs();
-
- const auto path_fs = uri_fs.IsNull()
- ? root
- : AllocatedPath::Build(root, uri_fs.c_str());
-
- recursive_watch_subdirectories(directory, path_fs,
- watch_directory_depth(directory));
- }
-
- 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.IsNull()) {
- const std::string uri_utf8 = uri_fs.ToUTF8();
- if (!uri_utf8.empty())
- inotify_queue->Enqueue(uri_utf8.c_str());
- }
- else
- inotify_queue->Enqueue("");
- }
-}
-
-void
-mpd_inotify_init(unsigned max_depth)
-{
- LogDebug(inotify_domain, "initializing inotify");
-
- const auto &path = mapper_get_music_directory_fs();
- if (path.IsNull()) {
- LogDebug(inotify_domain, "no music directory configured");
- return;
- }
-
- Error error;
- inotify_source = InotifySource::Create(*main_loop,
- mpd_inotify_callback, nullptr,
- error);
- if (inotify_source == nullptr) {
- LogError(error);
- return;
- }
-
- inotify_max_depth = max_depth;
-
- int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error);
- if (descriptor < 0) {
- LogError(error);
- delete inotify_source;
- inotify_source = nullptr;
- return;
- }
-
- inotify_root = new WatchDirectory(nullptr, path, descriptor);
-
- tree_add_watch_directory(inotify_root);
-
- recursive_watch_subdirectories(inotify_root, path, 0);
-
- inotify_queue = new InotifyQueue(*main_loop);
-
- LogDebug(inotify_domain, "watching music directory");
-}
-
-void
-mpd_inotify_finish(void)
-{
- if (inotify_source == nullptr)
- return;
-
- delete inotify_queue;
- delete inotify_source;
- delete inotify_root;
- inotify_directories.clear();
-}
diff --git a/src/InotifyUpdate.hxx b/src/InotifyUpdate.hxx
deleted file mode 100644
index 44d0b10b7..000000000
--- a/src/InotifyUpdate.hxx
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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(gcc_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
deleted file mode 100644
index 3af1e1686..000000000
--- a/src/InputInit.cxx
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "InputInit.hxx"
-#include "InputRegistry.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "ConfigData.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-extern constexpr Domain input_domain("input");
-
-/**
- * Find the "input" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the input plugin
- * @return the configuration block, or nullptr if none was configured
- */
-static const struct config_param *
-input_plugin_config(const char *plugin_name, Error &error)
-{
- const struct config_param *param = nullptr;
-
- while ((param = config_get_next_param(CONF_INPUT, param)) != nullptr) {
- const char *name = param->GetBlockValue("plugin");
- if (name == nullptr) {
- error.Format(input_domain,
- "input configuration without 'plugin' name in line %d",
- param->line);
- return nullptr;
- }
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return nullptr;
-}
-
-bool
-input_stream_global_init(Error &error)
-{
- const config_param empty;
-
- for (unsigned i = 0; input_plugins[i] != nullptr; ++i) {
- const InputPlugin *plugin = input_plugins[i];
-
- assert(plugin->name != nullptr);
- assert(*plugin->name != 0);
- assert(plugin->open != nullptr);
-
- const struct config_param *param =
- input_plugin_config(plugin->name, error);
- if (param == nullptr) {
- if (error.IsDefined())
- return false;
-
- param = &empty;
- } else if (!param->GetBlockValue("enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- if (plugin->init == nullptr || plugin->init(*param, error))
- input_plugins_enabled[i] = true;
- else {
- error.FormatPrefix("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 != nullptr)
- plugin->finish();
-}
diff --git a/src/InputInit.hxx b/src/InputInit.hxx
deleted file mode 100644
index 6afea1351..000000000
--- a/src/InputInit.hxx
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INPUT_INIT_HXX
-#define MPD_INPUT_INIT_HXX
-
-class Error;
-
-/**
- * Initializes this library and all input_stream implementations.
- */
-bool
-input_stream_global_init(Error &error);
-
-/**
- * Deinitializes this library and all input_stream implementations.
- */
-void input_stream_global_finish(void);
-
-#endif
diff --git a/src/InputPlugin.hxx b/src/InputPlugin.hxx
deleted file mode 100644
index 00226d195..000000000
--- a/src/InputPlugin.hxx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INPUT_PLUGIN_HXX
-#define MPD_INPUT_PLUGIN_HXX
-
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-
-#include <stddef.h>
-#include <stdint.h>
-
-struct config_param;
-struct InputStream;
-class Error;
-struct Tag;
-
-struct InputPlugin {
- typedef int64_t offset_type;
-
- const char *name;
-
- /**
- * Global initialization. This method is called when MPD starts.
- *
- * @return true on success, false if the plugin should be
- * disabled
- */
- bool (*init)(const config_param &param, Error &error);
-
- /**
- * Global deinitialization. Called once before MPD shuts
- * down (only if init() has returned true).
- */
- void (*finish)(void);
-
- InputStream *(*open)(const char *uri,
- Mutex &mutex, Cond &cond,
- Error &error);
- void (*close)(InputStream *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)(InputStream *is, Error &error);
-
- /**
- * Update the public attributes. Call before access. Can be
- * nullptr if the plugin always keeps its attributes up to date.
- */
- void (*update)(InputStream *is);
-
- Tag *(*tag)(InputStream *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)(InputStream *is);
-
- size_t (*read)(InputStream *is, void *ptr, size_t size,
- Error &error);
- bool (*eof)(InputStream *is);
- bool (*seek)(InputStream *is, offset_type offset, int whence,
- Error &error);
-};
-
-#endif
diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx
deleted file mode 100644
index aa6c06ed1..000000000
--- a/src/InputRegistry.cxx
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "InputRegistry.hxx"
-#include "util/Macros.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
-
-const InputPlugin *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
- nullptr
-};
-
-bool input_plugins_enabled[ARRAY_SIZE(input_plugins) - 1];
diff --git a/src/InputRegistry.hxx b/src/InputRegistry.hxx
deleted file mode 100644
index 8bc927a7a..000000000
--- a/src/InputRegistry.hxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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 InputPlugin *const input_plugins[];
-
-extern bool input_plugins_enabled[];
-
-#define input_plugins_for_each(plugin) \
- for (const InputPlugin *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
deleted file mode 100644
index 73b581d2d..000000000
--- a/src/InputStream.cxx
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "InputStream.hxx"
-#include "InputRegistry.hxx"
-#include "InputPlugin.hxx"
-#include "input/RewindInputPlugin.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <assert.h>
-#include <stdio.h> /* for SEEK_SET */
-
-static constexpr Domain input_domain("input");
-
-InputStream *
-InputStream::Open(const char *url,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- input_plugins_for_each_enabled(plugin) {
- InputStream *is;
-
- is = plugin->open(url, mutex, cond, error);
- if (is != nullptr) {
- assert(is->plugin.close != nullptr);
- assert(is->plugin.read != nullptr);
- assert(is->plugin.eof != nullptr);
- assert(!is->seekable || is->plugin.seek != nullptr);
-
- is = input_rewind_open(is);
-
- return is;
- } else if (error.IsDefined())
- return nullptr;
- }
-
- error.Set(input_domain, "Unrecognized URI");
- return nullptr;
-}
-
-bool
-InputStream::Check(Error &error)
-{
- return plugin.check == nullptr || plugin.check(this, error);
-}
-
-void
-InputStream::Update()
-{
- if (plugin.update != nullptr)
- plugin.update(this);
-}
-
-void
-InputStream::WaitReady()
-{
- while (true) {
- Update();
- if (ready)
- break;
-
- cond.wait(mutex);
- }
-}
-
-void
-InputStream::LockWaitReady()
-{
- const ScopeLock protect(mutex);
- WaitReady();
-}
-
-bool
-InputStream::CheapSeeking() const
-{
- return IsSeekable() && !uri_has_scheme(uri.c_str());
-}
-
-bool
-InputStream::Seek(offset_type _offset, int whence, Error &error)
-{
- if (plugin.seek == nullptr)
- return false;
-
- return plugin.seek(this, _offset, whence, error);
-}
-
-bool
-InputStream::LockSeek(offset_type _offset, int whence, Error &error)
-{
- if (plugin.seek == nullptr)
- return false;
-
- const ScopeLock protect(mutex);
- return Seek(_offset, whence, error);
-}
-
-bool
-InputStream::Rewind(Error &error)
-{
- return Seek(0, SEEK_SET, error);
-}
-
-bool
-InputStream::LockRewind(Error &error)
-{
- return LockSeek(0, SEEK_SET, error);
-}
-
-Tag *
-InputStream::ReadTag()
-{
- return plugin.tag != nullptr
- ? plugin.tag(this)
- : nullptr;
-}
-
-Tag *
-InputStream::LockReadTag()
-{
- if (plugin.tag == nullptr)
- return nullptr;
-
- const ScopeLock protect(mutex);
- return ReadTag();
-}
-
-bool
-InputStream::IsAvailable()
-{
- return plugin.available != nullptr
- ? plugin.available(this)
- : true;
-}
-
-size_t
-InputStream::Read(void *ptr, size_t _size, Error &error)
-{
-#if !CLANG_CHECK_VERSION(3,6)
- /* disabled on clang due to -Wtautological-pointer-compare */
- assert(ptr != nullptr);
-#endif
- assert(_size > 0);
-
- return plugin.read(this, ptr, _size, error);
-}
-
-size_t
-InputStream::LockRead(void *ptr, size_t _size, Error &error)
-{
-#if !CLANG_CHECK_VERSION(3,6)
- /* disabled on clang due to -Wtautological-pointer-compare */
- assert(ptr != nullptr);
-#endif
- assert(_size > 0);
-
- const ScopeLock protect(mutex);
- return Read(ptr, _size, error);
-}
-
-void
-InputStream::Close()
-{
- plugin.close(this);
-}
-
-bool
-InputStream::IsEOF()
-{
- return plugin.eof(this);
-}
-
-bool
-InputStream::LockIsEOF()
-{
- const ScopeLock protect(mutex);
- return IsEOF();
-}
-
diff --git a/src/InputStream.hxx b/src/InputStream.hxx
deleted file mode 100644
index b1bc9c4ab..000000000
--- a/src/InputStream.hxx
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INPUT_STREAM_HXX
-#define MPD_INPUT_STREAM_HXX
-
-#include "check.h"
-#include "thread/Mutex.hxx"
-#include "Compiler.h"
-
-#include <string>
-
-#include <assert.h>
-#include <stdint.h>
-
-class Cond;
-class Error;
-struct Tag;
-struct InputPlugin;
-
-struct InputStream {
- typedef int64_t offset_type;
-
- /**
- * the plugin which implements this input stream
- */
- const InputPlugin &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 nullptr.
- *
- * 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
- */
- offset_type size;
-
- /**
- * the current offset within the stream
- */
- offset_type offset;
-
- /**
- * the MIME content type of the resource, or empty if unknown.
- */
- std::string mime;
-
- InputStream(const InputPlugin &_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 != nullptr);
- }
-
- /**
- * Opens a new input stream. You may not access it until the "ready"
- * flag is set.
- *
- * @param mutex a mutex that is used to protect this object; must be
- * locked before calling any of the public methods
- * @param cond a cond that gets signalled when the state of
- * this object changes; may be nullptr if the caller doesn't want to get
- * notifications
- * @return an #InputStream object on success, nullptr on error
- */
- gcc_nonnull_all
- gcc_malloc
- static InputStream *Open(const char *uri, Mutex &mutex, Cond &cond,
- Error &error);
-
- /**
- * Close the input stream and free resources.
- *
- * The caller must not lock the mutex.
- */
- void Close();
-
- void Lock() {
- mutex.lock();
- }
-
- void Unlock() {
- mutex.unlock();
- }
-
- /**
- * Check for errors that may have occurred in the I/O thread.
- *
- * @return false on error
- */
- bool Check(Error &error);
-
- /**
- * Update the public attributes. Call before accessing attributes
- * such as "ready" or "offset".
- */
- void Update();
-
- /**
- * Wait until the stream becomes ready.
- *
- * The caller must lock the mutex.
- */
- void WaitReady();
-
- /**
- * Wrapper for WaitReady() which locks and unlocks the mutex;
- * the caller must not be holding it already.
- */
- void LockWaitReady();
-
- gcc_pure
- const char *GetMimeType() const {
- assert(ready);
-
- return mime.empty() ? nullptr : mime.c_str();
- }
-
- gcc_nonnull_all
- void OverrideMimeType(const char *_mime) {
- assert(ready);
-
- mime = _mime;
- }
-
- gcc_pure
- offset_type GetSize() const {
- assert(ready);
-
- return size;
- }
-
- gcc_pure
- offset_type GetOffset() const {
- assert(ready);
-
- return offset;
- }
-
- gcc_pure
- bool IsSeekable() const {
- assert(ready);
-
- return seekable;
- }
-
- /**
- * Determines whether seeking is cheap. This is true for local files.
- */
- gcc_pure
- bool CheapSeeking() const;
-
- /**
- * Seeks to the specified position in the stream. This will most
- * likely fail if the "seekable" flag is false.
- *
- * The caller must lock the mutex.
- *
- * @param offset the relative offset
- * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
- */
- bool Seek(offset_type offset, int whence, Error &error);
-
- /**
- * Wrapper for Seek() which locks and unlocks the mutex; the
- * caller must not be holding it already.
- */
- bool LockSeek(offset_type offset, int whence, Error &error);
-
- /**
- * Rewind to the beginning of the stream. This is a wrapper
- * for Seek(0, SEEK_SET, error).
- */
- bool Rewind(Error &error);
- bool LockRewind(Error &error);
-
- /**
- * Returns true if the stream has reached end-of-file.
- *
- * The caller must lock the mutex.
- */
- gcc_pure
- bool IsEOF();
-
- /**
- * Wrapper for IsEOF() which locks and unlocks the mutex; the
- * caller must not be holding it already.
- */
- gcc_pure
- bool LockIsEOF();
-
- /**
- * Reads the tag from the stream.
- *
- * The caller must lock the mutex.
- *
- * @return a tag object which must be freed by the caller, or
- * nullptr if the tag has not changed since the last call
- */
- gcc_malloc
- Tag *ReadTag();
-
- /**
- * Wrapper for ReadTag() which locks and unlocks the mutex;
- * the caller must not be holding it already.
- */
- gcc_malloc
- Tag *LockReadTag();
-
- /**
- * 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.
- *
- * The caller must lock the mutex.
- */
- gcc_pure
- bool IsAvailable();
-
- /**
- * Reads data from the stream into the caller-supplied buffer.
- * Returns 0 on error or eof (check with IsEOF()).
- *
- * The caller must lock the mutex.
- *
- * @param is the InputStream object
- * @param ptr the buffer to read into
- * @param size the maximum number of bytes to read
- * @return the number of bytes read
- */
- gcc_nonnull_all
- size_t Read(void *ptr, size_t size, Error &error);
-
- /**
- * Wrapper for Read() which locks and unlocks the mutex;
- * the caller must not be holding it already.
- */
- gcc_nonnull_all
- size_t LockRead(void *ptr, size_t size, Error &error);
-};
-
-#endif
diff --git a/src/Instance.cxx b/src/Instance.cxx
index daad94212..232cd21df 100644
--- a/src/Instance.cxx
+++ b/src/Instance.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,28 +21,82 @@
#include "Instance.hxx"
#include "Partition.hxx"
#include "Idle.hxx"
+#include "Stats.hxx"
+
+#ifdef ENABLE_DATABASE
+#include "db/DatabaseError.hxx"
+#include "db/LightSong.hxx"
+
+#ifdef ENABLE_SQLITE
+#include "sticker/StickerDatabase.hxx"
+#include "sticker/SongSticker.hxx"
+#endif
+
+Database *
+Instance::GetDatabase(Error &error)
+{
+ if (database == nullptr)
+ error.Set(db_domain, DB_DISABLED, "No database");
+ return database;
+}
+
+#endif
void
-Instance::DeleteSong(const Song &song)
+Instance::TagModified()
{
- partition->DeleteSong(song);
+ partition->TagModified();
}
void
-Instance::DatabaseModified()
+Instance::SyncWithPlayer()
+{
+ partition->SyncWithPlayer();
+}
+
+#ifdef ENABLE_DATABASE
+
+void
+Instance::OnDatabaseModified()
{
- partition->DatabaseModified();
+ assert(database != nullptr);
+
+ /* propagate the change to all subsystems */
+
+ stats_invalidate();
+ partition->DatabaseModified(*database);
idle_add(IDLE_DATABASE);
}
void
-Instance::TagModified()
+Instance::OnDatabaseSongRemoved(const LightSong &song)
{
- partition->TagModified();
+ assert(database != nullptr);
+
+#ifdef ENABLE_SQLITE
+ /* if the song has a sticker, remove it */
+ if (sticker_enabled())
+ sticker_song_delete(song);
+#endif
+
+ const auto uri = song.GetURI();
+ partition->DeleteSong(uri.c_str());
}
+#endif
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+
void
-Instance::SyncWithPlayer()
+Instance::FoundNeighbor(gcc_unused const NeighborInfo &info)
{
- partition->SyncWithPlayer();
+ idle_add(IDLE_NEIGHBOR);
}
+
+void
+Instance::LostNeighbor(gcc_unused const NeighborInfo &info)
+{
+ idle_add(IDLE_NEIGHBOR);
+}
+
+#endif
diff --git a/src/Instance.hxx b/src/Instance.hxx
index a0dfd1b94..fa7711ab9 100644
--- a/src/Instance.hxx
+++ b/src/Instance.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,23 +21,76 @@
#define MPD_INSTANCE_HXX
#include "check.h"
+#include "Compiler.h"
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+#include "neighbor/Listener.hxx"
+class NeighborGlue;
+#endif
+
+#ifdef ENABLE_DATABASE
+#include "db/DatabaseListener.hxx"
+class Database;
+class Storage;
+class UpdateService;
+#endif
+
+class EventLoop;
+class Error;
class ClientList;
struct Partition;
-struct Song;
-struct Instance {
+struct Instance final
+#if defined(ENABLE_DATABASE) || defined(ENABLE_NEIGHBOR_PLUGINS)
+ :
+#endif
+#ifdef ENABLE_DATABASE
+ public DatabaseListener
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ ,
+#endif
+#endif
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ public NeighborListener
+#endif
+{
+ EventLoop *event_loop;
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ NeighborGlue *neighbors;
+#endif
+
+#ifdef ENABLE_DATABASE
+ Database *database;
+
+ /**
+ * This is really a #CompositeStorage. To avoid heavy include
+ * dependencies, we declare it as just #Storage.
+ */
+ Storage *storage;
+
+ UpdateService *update;
+#endif
+
ClientList *client_list;
Partition *partition;
- void DeleteSong(const Song &song);
+ Instance() {
+#ifdef ENABLE_DATABASE
+ storage = nullptr;
+ update = nullptr;
+#endif
+ }
+#ifdef ENABLE_DATABASE
/**
- * The database has been modified. Propagate the change to
- * all subsystems.
+ * Returns the global #Database instance. May return nullptr
+ * if this MPD configuration has no database (no
+ * music_directory was configured).
*/
- void DatabaseModified();
+ Database *GetDatabase(Error &error);
+#endif
/**
* A tag in the play queue has been modified by the player
@@ -49,6 +102,18 @@ struct Instance {
* Synchronize the player with the play queue.
*/
void SyncWithPlayer();
+
+private:
+#ifdef ENABLE_DATABASE
+ virtual void OnDatabaseModified() override;
+ virtual void OnDatabaseSongRemoved(const LightSong &song) override;
+#endif
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ /* virtual methods from class NeighborListener */
+ virtual void FoundNeighbor(const NeighborInfo &info) override;
+ virtual void LostNeighbor(const NeighborInfo &info) override;
+#endif
};
#endif
diff --git a/src/Listen.cxx b/src/Listen.cxx
index d5c132545..d48d795d1 100644
--- a/src/Listen.cxx
+++ b/src/Listen.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,10 @@
#include "config.h"
#include "Listen.hxx"
-#include "Main.hxx"
-#include "Instance.hxx"
-#include "Client.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
+#include "client/Client.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
#include "event/ServerSocket.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -43,13 +41,16 @@ static constexpr Domain listen_domain("listen");
#define DEFAULT_PORT 6600
class ClientListener final : public ServerSocket {
+ Partition &partition;
+
public:
- ClientListener():ServerSocket(*main_loop) {}
+ ClientListener(EventLoop &_loop, Partition &_partition)
+ :ServerSocket(_loop), partition(_partition) {}
private:
virtual void OnAccept(int fd, const sockaddr &address,
size_t address_length, int uid) {
- client_new(*main_loop, *instance->partition,
+ client_new(GetEventLoop(), partition,
fd, &address, address_length, uid);
}
};
@@ -76,10 +77,11 @@ listen_add_config_param(unsigned int port,
}
}
+#ifdef ENABLE_SYSTEMD_DAEMON
+
static bool
listen_systemd_activation(Error &error_r)
{
-#ifdef ENABLE_SYSTEMD_DAEMON
int n = sd_listen_fds(true);
if (n <= 0) {
if (n < 0)
@@ -94,29 +96,26 @@ listen_systemd_activation(Error &error_r)
return false;
return true;
-#else
- (void)error_r;
- return false;
-#endif
}
+#endif
+
bool
-listen_global_init(Error &error)
+listen_global_init(EventLoop &loop, Partition &partition, Error &error)
{
- 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, nullptr);
- bool success;
+ config_get_param(CONF_BIND_TO_ADDRESS);
- listen_socket = new ClientListener();
+ listen_socket = new ClientListener(loop, partition);
+#ifdef ENABLE_SYSTEMD_DAEMON
if (listen_systemd_activation(error))
return true;
if (error.IsDefined())
return false;
+#endif
if (param != nullptr) {
/* "bind_to_address" is configured, create listeners
@@ -130,16 +129,12 @@ listen_global_init(Error &error)
param->line);
return false;
}
-
- param = config_get_next_param(CONF_BIND_TO_ADDRESS,
- param);
- } while (param != nullptr);
+ } while ((param = param->next) != nullptr);
} else {
/* no "bind_to_address" configured, bind the
configured port on all interfaces */
- success = listen_socket->AddPort(port, error);
- if (!success) {
+ if (!listen_socket->AddPort(port, error)) {
delete listen_socket;
error.FormatPrefix("Failed to listen on *:%d: ", port);
return false;
diff --git a/src/Listen.hxx b/src/Listen.hxx
index a6fdb2f1c..d74c1d233 100644
--- a/src/Listen.hxx
+++ b/src/Listen.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,12 +20,14 @@
#ifndef MPD_LISTEN_HXX
#define MPD_LISTEN_HXX
+class EventLoop;
class Error;
+struct Partition;
extern int listen_port;
bool
-listen_global_init(Error &error);
+listen_global_init(EventLoop &loop, Partition &partition, Error &error);
void listen_global_finish(void);
diff --git a/src/Log.cxx b/src/Log.cxx
index a46c0ced8..ba691581b 100644
--- a/src/Log.cxx
+++ b/src/Log.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,62 +19,19 @@
#include "config.h"
#include "LogV.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "system/fd_util.h"
-#include "system/FatalError.hxx"
-#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
-
-#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <fcntl.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
+#include <string.h>
#include <errno.h>
-static GLogLevelFlags
-ToGLib(LogLevel level)
-{
- switch (level) {
- case LogLevel::DEBUG:
- return G_LOG_LEVEL_DEBUG;
-
- case LogLevel::INFO:
- return G_LOG_LEVEL_INFO;
-
- case LogLevel::DEFAULT:
- return G_LOG_LEVEL_MESSAGE;
-
- case LogLevel::WARNING:
- case LogLevel::ERROR:
- return G_LOG_LEVEL_WARNING;
- }
-
- assert(false);
- gcc_unreachable();
-}
-
-void
-Log(const Domain &domain, LogLevel level, const char *msg)
-{
- g_log(domain.GetName(), ToGLib(level), "%s", msg);
-}
-
void
LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap)
{
- g_logv(domain.GetName(), ToGLib(level), fmt, ap);
+ char msg[1024];
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ Log(domain, level, msg);
}
void
@@ -159,7 +116,7 @@ FormatError(const Error &error, const char *fmt, ...)
void
LogErrno(const Domain &domain, int e, const char *msg)
{
- LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, g_strerror(e));
+ LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
}
void
diff --git a/src/Log.hxx b/src/Log.hxx
index 920a8d8a3..15077e374 100644
--- a/src/Log.hxx
+++ b/src/Log.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,47 +20,12 @@
#ifndef MPD_LOG_HXX
#define MPD_LOG_HXX
+#include "LogLevel.hxx"
#include "Compiler.h"
-#ifdef WIN32
-#include <windows.h>
-/* damn you, windows.h! */
-#ifdef ERROR
-#undef ERROR
-#endif
-#endif
-
class Error;
class Domain;
-enum class LogLevel {
- /**
- * Debug message for developers.
- */
- DEBUG,
-
- /**
- * Unimportant informational message.
- */
- INFO,
-
- /**
- * Interesting informational message.
- */
- DEFAULT,
-
- /**
- * Warning: something may be wrong.
- */
- WARNING,
-
- /**
- * An error has occurred, an operation could not finish
- * successfully.
- */
- ERROR,
-};
-
void
Log(const Domain &domain, LogLevel level, const char *msg);
diff --git a/src/LogBackend.cxx b/src/LogBackend.cxx
new file mode 100644
index 000000000..04c2e6324
--- /dev/null
+++ b/src/LogBackend.cxx
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LogBackend.hxx"
+#include "Log.hxx"
+#include "util/Domain.hxx"
+#include "util/StringUtil.hxx"
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_SYSLOG
+#include <syslog.h>
+#endif
+
+#ifdef ANDROID
+#include <android/log.h>
+
+static int
+ToAndroidLogLevel(LogLevel log_level)
+{
+ switch (log_level) {
+ case LogLevel::DEBUG:
+ return ANDROID_LOG_DEBUG;
+
+ case LogLevel::INFO:
+ case LogLevel::DEFAULT:
+ return ANDROID_LOG_INFO;
+
+ case LogLevel::WARNING:
+ return ANDROID_LOG_WARN;
+
+ case LogLevel::ERROR:
+ return ANDROID_LOG_ERROR;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+#else
+
+static LogLevel log_threshold = LogLevel::INFO;
+
+#ifdef HAVE_GLIB
+static const char *log_charset;
+#endif
+
+static bool enable_timestamp;
+
+#ifdef HAVE_SYSLOG
+static bool enable_syslog;
+#endif
+
+void
+SetLogThreshold(LogLevel _threshold)
+{
+ log_threshold = _threshold;
+}
+
+#ifdef HAVE_GLIB
+
+void
+SetLogCharset(const char *_charset)
+{
+ log_charset = _charset;
+}
+
+#endif
+
+void
+EnableLogTimestamp()
+{
+#ifdef HAVE_SYSLOG
+ assert(!enable_syslog);
+#endif
+ assert(!enable_timestamp);
+
+ enable_timestamp = true;
+}
+
+static const char *log_date(void)
+{
+ static constexpr size_t LOG_DATE_BUF_SIZE = 16;
+ static char buf[LOG_DATE_BUF_SIZE];
+ time_t t = time(nullptr);
+ strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t));
+ return buf;
+}
+
+/**
+ * Determines the length of the string excluding trailing whitespace
+ * characters.
+ */
+static int
+chomp_length(const char *p)
+{
+ size_t length = strlen(p);
+ return StripRight(p, length);
+}
+
+#ifdef HAVE_SYSLOG
+
+static int
+ToSysLogLevel(LogLevel log_level)
+{
+ switch (log_level) {
+ case LogLevel::DEBUG:
+ return LOG_DEBUG;
+
+ case LogLevel::INFO:
+ return LOG_INFO;
+
+ case LogLevel::DEFAULT:
+ return LOG_NOTICE;
+
+ case LogLevel::WARNING:
+ return LOG_WARNING;
+
+ case LogLevel::ERROR:
+ return LOG_ERR;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+static void
+SysLog(const Domain &domain, LogLevel log_level, const char *message)
+{
+ syslog(ToSysLogLevel(log_level), "%s: %.*s",
+ domain.GetName(),
+ chomp_length(message), message);
+}
+
+void
+LogInitSysLog()
+{
+ openlog(PACKAGE, 0, LOG_DAEMON);
+ enable_syslog = true;
+}
+
+void
+LogFinishSysLog()
+{
+ if (enable_syslog)
+ closelog();
+}
+
+#endif
+
+static void
+FileLog(const Domain &domain, const char *message)
+{
+#ifdef HAVE_GLIB
+ char *converted;
+
+ if (log_charset != nullptr) {
+ converted = g_convert_with_fallback(message, -1,
+ log_charset, "utf-8",
+ nullptr, nullptr,
+ nullptr, nullptr);
+ if (converted != nullptr)
+ message = converted;
+ } else
+ converted = nullptr;
+#endif
+
+ fprintf(stderr, "%s%s: %.*s\n",
+ enable_timestamp ? log_date() : "",
+ domain.GetName(),
+ chomp_length(message), message);
+
+#ifdef WIN32
+ /* force-flush the log file, because setvbuf() does not seem
+ to have an effect on WIN32 */
+ fflush(stderr);
+#endif
+
+#ifdef HAVE_GLIB
+ g_free(converted);
+#endif
+}
+
+#endif /* !ANDROID */
+
+void
+Log(const Domain &domain, LogLevel level, const char *msg)
+{
+#ifdef ANDROID
+ __android_log_print(ToAndroidLogLevel(level), "MPD",
+ "%s: %s", domain.GetName(), msg);
+#else
+
+ if (level < log_threshold)
+ return;
+
+#ifdef HAVE_SYSLOG
+ if (enable_syslog) {
+ SysLog(domain, level, msg);
+ return;
+ }
+#endif
+
+ FileLog(domain, msg);
+#endif /* !ANDROID */
+}
diff --git a/src/LogBackend.hxx b/src/LogBackend.hxx
new file mode 100644
index 000000000..23df2037e
--- /dev/null
+++ b/src/LogBackend.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_LOG_BACKEND_HXX
+#define MPD_LOG_BACKEND_HXX
+
+#include "check.h"
+#include "LogLevel.hxx"
+
+void
+SetLogThreshold(LogLevel _threshold);
+
+#ifdef HAVE_GLIB
+
+void
+SetLogCharset(const char *_charset);
+
+#endif
+
+void
+EnableLogTimestamp();
+
+void
+LogInitSysLog();
+
+void
+LogFinishSysLog();
+
+#endif /* LOG_H */
diff --git a/src/LogInit.cxx b/src/LogInit.cxx
index 41d13a5e8..117c6d8dc 100644
--- a/src/LogInit.cxx
+++ b/src/LogInit.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,48 +19,38 @@
#include "config.h"
#include "LogInit.hxx"
+#include "LogBackend.hxx"
#include "Log.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "system/fd_util.h"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
#include "system/FatalError.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include "util/CharUtil.hxx"
#include "system/FatalError.hxx"
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <string.h>
-#include <stdarg.h>
#include <fcntl.h>
#include <stdio.h>
-#include <stdlib.h>
#include <time.h>
#include <unistd.h>
-#include <errno.h>
-#include <glib.h>
-
-#ifdef HAVE_SYSLOG
-#include <syslog.h>
-#endif
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+#define LOG_LEVEL_SECURE LogLevel::INFO
#define LOG_DATE_BUF_SIZE 16
#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
static constexpr Domain log_domain("log");
-static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE;
-
-static const char *log_charset;
+#ifndef ANDROID
-static bool stdout_mode = true;
static int out_fd;
static AllocatedPath out_path = AllocatedPath::Null();
@@ -73,66 +63,6 @@ static void redirect_logs(int fd)
FatalSystemError("Failed to dup2 stderr");
}
-static const char *log_date(void)
-{
- static char buf[LOG_DATE_BUF_SIZE];
- time_t t = time(nullptr);
- strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t));
- return buf;
-}
-
-/**
- * Determines the length of the string excluding trailing whitespace
- * characters.
- */
-static int
-chomp_length(const char *p)
-{
- size_t length = strlen(p);
-
- while (length > 0 && IsWhitespaceOrNull(p[length - 1]))
- --length;
-
- return (int)length;
-}
-
-static void
-file_log_func(const gchar *domain,
- GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- char *converted;
-
- if (log_level > log_threshold)
- return;
-
- if (log_charset != nullptr) {
- converted = g_convert_with_fallback(message, -1,
- log_charset, "utf-8",
- nullptr, nullptr,
- nullptr, nullptr);
- if (converted != nullptr)
- message = converted;
- } else
- converted = nullptr;
-
- if (domain == nullptr)
- domain = "";
-
- fprintf(stderr, "%s%s%s%.*s\n",
- stdout_mode ? "" : log_date(),
- domain, *domain == 0 ? "" : ": ",
- chomp_length(message), message);
-
- g_free(converted);
-}
-
-static void
-log_init_stdout(void)
-{
- g_log_set_default_handler(file_log_func, nullptr);
-}
-
static int
open_log_file(void)
{
@@ -154,112 +84,66 @@ log_init_file(unsigned line, Error &error)
return false;
}
- g_log_set_default_handler(file_log_func, nullptr);
+ EnableLogTimestamp();
return true;
}
-#ifdef HAVE_SYSLOG
-
-static int
-glib_to_syslog_level(GLogLevelFlags log_level)
-{
- switch (log_level & G_LOG_LEVEL_MASK) {
- case G_LOG_LEVEL_ERROR:
- case G_LOG_LEVEL_CRITICAL:
- return LOG_ERR;
-
- case G_LOG_LEVEL_WARNING:
- return LOG_WARNING;
-
- case G_LOG_LEVEL_MESSAGE:
- return LOG_NOTICE;
-
- case G_LOG_LEVEL_INFO:
- return LOG_INFO;
-
- case G_LOG_LEVEL_DEBUG:
- return LOG_DEBUG;
-
- default:
- return LOG_NOTICE;
- }
-}
-
-static void
-syslog_log_func(const gchar *domain,
- GLogLevelFlags log_level, const gchar *message,
- gcc_unused gpointer user_data)
-{
- if (stdout_mode) {
- /* fall back to the file log function during
- startup */
- file_log_func(domain, log_level,
- message, user_data);
- return;
- }
-
- if (log_level > log_threshold)
- return;
-
- if (domain == nullptr)
- domain = "";
-
- syslog(glib_to_syslog_level(log_level), "%s%s%.*s",
- domain, *domain == 0 ? "" : ": ",
- chomp_length(message), message);
-}
-
-static void
-log_init_syslog(void)
-{
- assert(out_path.IsNull());
-
- openlog(PACKAGE, 0, LOG_DAEMON);
- g_log_set_default_handler(syslog_log_func, nullptr);
-}
-
-#endif
-
-static inline GLogLevelFlags
+static inline LogLevel
parse_log_level(const char *value, unsigned line)
{
if (0 == strcmp(value, "default"))
- return G_LOG_LEVEL_MESSAGE;
+ return LogLevel::DEFAULT;
if (0 == strcmp(value, "secure"))
return LOG_LEVEL_SECURE;
else if (0 == strcmp(value, "verbose"))
- return G_LOG_LEVEL_DEBUG;
+ return LogLevel::DEBUG;
else {
FormatFatalError("unknown log level \"%s\" at line %u",
value, line);
- return G_LOG_LEVEL_MESSAGE;
}
}
+#endif
+
void
log_early_init(bool verbose)
{
- if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
+#ifdef ANDROID
+ (void)verbose;
+#else
+ /* force stderr to be line-buffered */
+ setvbuf(stderr, nullptr, _IOLBF, 0);
- log_init_stdout();
+ if (verbose)
+ SetLogThreshold(LogLevel::DEBUG);
+#endif
}
bool
log_init(bool verbose, bool use_stdout, Error &error)
{
+#ifdef ANDROID
+ (void)verbose;
+ (void)use_stdout;
+ (void)error;
+
+ return true;
+#else
const struct config_param *param;
- g_get_charset(&log_charset);
+#ifdef HAVE_GLIB
+ const char *charset;
+ g_get_charset(&charset);
+ SetLogCharset(charset);
+#endif
if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
+ SetLogThreshold(LogLevel::DEBUG);
else if ((param = config_get_param(CONF_LOG_LEVEL)) != nullptr)
- log_threshold = parse_log_level(param->value.c_str(),
- param->line);
+ SetLogThreshold(parse_log_level(param->value.c_str(),
+ param->line));
if (use_stdout) {
- log_init_stdout();
return true;
} else {
param = config_get_param(CONF_LOG_FILE);
@@ -267,7 +151,7 @@ log_init(bool verbose, bool use_stdout, Error &error)
#ifdef HAVE_SYSLOG
/* no configuration: default to syslog (if
available) */
- log_init_syslog();
+ LogInitSysLog();
return true;
#else
error.Set(log_domain,
@@ -276,7 +160,7 @@ log_init(bool verbose, bool use_stdout, Error &error)
#endif
#ifdef HAVE_SYSLOG
} else if (strcmp(param->value.c_str(), "syslog") == 0) {
- log_init_syslog();
+ LogInitSysLog();
return true;
#endif
} else {
@@ -285,56 +169,70 @@ log_init(bool verbose, bool use_stdout, Error &error)
log_init_file(param->line, error);
}
}
+#endif
}
+#ifndef ANDROID
+
static void
close_log_files(void)
{
- if (stdout_mode)
- return;
-
#ifdef HAVE_SYSLOG
- if (out_path.IsNull())
- closelog();
+ LogFinishSysLog();
#endif
}
+#endif
+
void
log_deinit(void)
{
+#ifndef ANDROID
close_log_files();
out_path = AllocatedPath::Null();
+#endif
}
-
void setup_log_output(bool use_stdout)
{
+#ifdef ANDROID
+ (void)use_stdout;
+#else
+ if (use_stdout)
+ return;
+
fflush(nullptr);
- if (!use_stdout) {
-#ifndef WIN32
- if (out_path.IsNull())
- out_fd = open("/dev/null", O_WRONLY);
+
+ if (out_fd < 0) {
+#ifdef WIN32
+ return;
+#else
+ out_fd = open("/dev/null", O_WRONLY);
+ if (out_fd < 0)
+ return;
#endif
+ }
- if (out_fd >= 0) {
- redirect_logs(out_fd);
- close(out_fd);
- }
+ redirect_logs(out_fd);
+ close(out_fd);
+ out_fd = -1;
- stdout_mode = false;
- log_charset = nullptr;
- }
+#ifdef HAVE_GLIB
+ SetLogCharset(nullptr);
+#endif
+#endif
}
int cycle_log_files(void)
{
+#ifdef ANDROID
+ return 0;
+#else
int fd;
- if (stdout_mode || out_path.IsNull())
+ if (out_path.IsNull())
return 0;
- assert(!out_path.IsNull());
-
FormatDebug(log_domain, "Cycling log files");
close_log_files();
@@ -348,6 +246,9 @@ int cycle_log_files(void)
}
redirect_logs(fd);
+ close(fd);
+
FormatDebug(log_domain, "Done cycling log files");
return 0;
+#endif
}
diff --git a/src/LogInit.hxx b/src/LogInit.hxx
index 2369fcdca..d0bcb40f2 100644
--- a/src/LogInit.hxx
+++ b/src/LogInit.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/LogLevel.hxx b/src/LogLevel.hxx
new file mode 100644
index 000000000..2614a67d1
--- /dev/null
+++ b/src/LogLevel.hxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_LOG_LEVEL_HXX
+#define MPD_LOG_LEVEL_HXX
+
+#ifdef WIN32
+#include <windows.h>
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+enum class LogLevel {
+ /**
+ * Debug message for developers.
+ */
+ DEBUG,
+
+ /**
+ * Unimportant informational message.
+ */
+ INFO,
+
+ /**
+ * Interesting informational message.
+ */
+ DEFAULT,
+
+ /**
+ * Warning: something may be wrong.
+ */
+ WARNING,
+
+ /**
+ * An error has occurred, an operation could not finish
+ * successfully.
+ */
+ ERROR,
+};
+
+#endif
diff --git a/src/LogV.hxx b/src/LogV.hxx
index 4bd4f801d..6b16f82b4 100644
--- a/src/LogV.hxx
+++ b/src/LogV.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,7 @@
#ifndef MPD_LOGV_HXX
#define MPD_LOGV_HXX
-#include "Log.hxx"
+#include "Log.hxx" // IWYU pragma: export
#include <stdarg.h>
diff --git a/src/Main.cxx b/src/Main.cxx
index b45e2c3ae..26d4e7ae4 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,67 +23,87 @@
#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 "client/Client.hxx"
+#include "client/ClientList.hxx"
#include "command/AllCommands.hxx"
#include "Partition.hxx"
-#include "Volume.hxx"
-#include "OutputAll.hxx"
#include "tag/TagConfig.hxx"
#include "ReplayGainConfig.hxx"
#include "Idle.hxx"
-#include "SignalHandlers.hxx"
#include "Log.hxx"
#include "LogInit.hxx"
#include "GlobalEvents.hxx"
-#include "InputInit.hxx"
+#include "input/Init.hxx"
#include "event/Loop.hxx"
#include "IOThread.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Config.hxx"
-#include "PlaylistRegistry.hxx"
-#include "ZeroconfGlue.hxx"
-#include "DecoderList.hxx"
+#include "playlist/PlaylistRegistry.hxx"
+#include "zeroconf/ZeroconfGlue.hxx"
+#include "decoder/DecoderList.hxx"
#include "AudioConfig.hxx"
-#include "pcm/PcmResample.hxx"
-#include "Daemon.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "unix/SignalHandlers.hxx"
+#include "unix/Daemon.hxx"
#include "system/FatalError.hxx"
+#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "thread/Id.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
-#include "ConfigDefaults.hxx"
-#include "ConfigOption.hxx"
+#include "thread/Slack.hxx"
+#include "lib/icu/Init.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigDefaults.hxx"
+#include "config/ConfigOption.hxx"
+#include "config/ConfigError.hxx"
#include "Stats.hxx"
+#ifdef ENABLE_DATABASE
+#include "db/update/Service.hxx"
+#include "db/Configured.hxx"
+#include "db/DatabasePlugin.hxx"
+#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
+#include "storage/Configured.hxx"
+#include "storage/CompositeStorage.hxx"
#ifdef ENABLE_INOTIFY
-#include "InotifyUpdate.hxx"
+#include "db/update/InotifyUpdate.hxx"
+#endif
+#endif
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+#include "neighbor/Glue.hxx"
#endif
#ifdef ENABLE_SQLITE
-#include "StickerDatabase.hxx"
+#include "sticker/StickerDatabase.hxx"
#endif
#ifdef ENABLE_ARCHIVE
-#include "ArchiveList.hxx"
+#include "archive/ArchiveList.hxx"
+#endif
+
+#ifdef ANDROID
+#include "java/Global.hxx"
+#include "java/File.hxx"
+#include "android/Environment.hxx"
+#include "android/Context.hxx"
+#include "fs/StandardDirectory.hxx"
+#include "fs/FileSystem.hxx"
+#include "org_musicpd_Bridge.h"
#endif
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
-#include <unistd.h>
#include <stdlib.h>
-#include <errno.h>
-#include <string.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
@@ -94,18 +114,27 @@
#include <ws2tcpip.h>
#endif
+#ifdef __BLOCKS__
+#include <dispatch/dispatch.h>
+#endif
+
+#include <limits.h>
+
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
static constexpr Domain main_domain("main");
-ThreadId main_thread;
-EventLoop *main_loop;
+#ifdef ANDROID
+Context *context;
+#endif
Instance *instance;
static StateFile *state_file;
+#ifndef ANDROID
+
static bool
glue_daemonize_init(const struct options *options, Error &error)
{
@@ -123,28 +152,33 @@ glue_daemonize_init(const struct options *options, Error &error)
return true;
}
+#endif
+
static bool
glue_mapper_init(Error &error)
{
- auto music_dir = config_get_path(CONF_MUSIC_DIR, error);
- if (music_dir.IsNull() && error.IsDefined())
- return false;
-
auto playlist_dir = config_get_path(CONF_PLAYLIST_DIR, error);
if (playlist_dir.IsNull() && error.IsDefined())
return false;
- if (music_dir.IsNull()) {
- const char *path =
- g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
- if (path != nullptr) {
- music_dir = AllocatedPath::FromUTF8(path, error);
- if (music_dir.IsNull())
- return false;
- }
- }
+ mapper_init(std::move(playlist_dir));
+ return true;
+}
+
+#ifdef ENABLE_DATABASE
- mapper_init(std::move(music_dir), std::move(playlist_dir));
+static bool
+InitStorage(Error &error)
+{
+ Storage *storage = CreateConfiguredStorage(io_thread_get(), error);
+ if (storage == nullptr)
+ return !error.IsDefined();
+
+ assert(!error.IsDefined());
+
+ CompositeStorage *composite = new CompositeStorage();
+ instance->storage = composite;
+ composite->Mount("", storage);
return true;
}
@@ -156,50 +190,60 @@ glue_mapper_init(Error &error)
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);
+ Error error;
+ instance->database =
+ CreateConfiguredDatabase(*instance->event_loop, *instance,
+ error);
+ if (instance->database == nullptr) {
+ if (error.IsDefined())
+ FatalError(error);
+ else
+ return true;
+ }
- if (param != nullptr && path != nullptr)
- LogWarning(main_domain,
- "Found both 'database' and 'db_file' setting - ignoring the latter");
+ if (instance->database->GetPlugin().flags & DatabasePlugin::FLAG_REQUIRE_STORAGE) {
+ if (!InitStorage(error))
+ FatalError(error);
- if (!mapper_has_music_directory()) {
- if (param != nullptr)
- LogDefault(main_domain,
+ if (instance->storage == nullptr) {
+ delete instance->database;
+ instance->database = nullptr;
+ LogDefault(config_domain,
"Found database setting without "
"music_directory - disabling database");
- if (path != nullptr)
- LogDefault(main_domain,
- "Found db_file setting without "
- "music_directory - disabling database");
- return true;
- }
-
- struct config_param *allocated = nullptr;
-
- if (param == nullptr && path != nullptr) {
- allocated = new config_param("database", path->line);
- allocated->AddBlockParam("path", path->value.c_str(),
- path->line);
- param = allocated;
+ return true;
+ }
+ } else {
+ if (IsStorageConfigured())
+ LogDefault(config_domain,
+ "Ignoring the storage configuration "
+ "because the database does not need it");
}
- if (param == nullptr)
- return true;
-
- Error error;
- if (!DatabaseGlobalInit(*param, error))
+ if (!instance->database->Open(error))
FatalError(error);
- delete allocated;
+ if (!instance->database->IsPlugin(simple_db_plugin))
+ return true;
- if (!DatabaseGlobalOpen(error))
- FatalError(error);
+ SimpleDatabase &db = *(SimpleDatabase *)instance->database;
+ instance->update = new UpdateService(*instance->event_loop, db,
+ static_cast<CompositeStorage &>(*instance->storage),
+ *instance);
/* run database update after daemonization? */
- return !db_is_simple() || db_exists();
+ return db.FileExists();
}
+static bool
+InitDatabaseAndStorage()
+{
+ const bool create_db = !glue_db_init_and_load();
+ return create_db;
+}
+
+#endif
+
/**
* Configure and initialize the sticker subsystem.
*/
@@ -224,11 +268,27 @@ static bool
glue_state_file_init(Error &error)
{
auto path_fs = config_get_path(CONF_STATE_FILE, error);
- if (path_fs.IsNull())
- return !error.IsDefined();
+ if (path_fs.IsNull()) {
+ if (error.IsDefined())
+ return false;
- state_file = new StateFile(std::move(path_fs),
- *instance->partition, *main_loop);
+#ifdef ANDROID
+ const auto cache_dir = GetUserCacheDir();
+ if (cache_dir.IsNull())
+ return true;
+
+ path_fs = AllocatedPath::Build(cache_dir, "state");
+#else
+ return true;
+#endif
+ }
+
+ unsigned interval = config_get_unsigned(CONF_STATE_FILE_INTERVAL,
+ StateFile::DEFAULT_INTERVAL);
+
+ state_file = new StateFile(std::move(path_fs), interval,
+ *instance->partition,
+ *instance->event_loop);
state_file->Read();
return true;
}
@@ -240,9 +300,8 @@ static void winsock_init(void)
{
#ifdef WIN32
WSADATA sockinfo;
- int retval;
- retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
+ int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo);
if(retval != 0)
FormatFatalError("Attempt to open Winsock2 failed; error code %d",
retval);
@@ -260,14 +319,11 @@ 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;
+ size_t buffer_size;
param = config_get_param(CONF_AUDIO_BUFFER_SIZE);
if (param != nullptr) {
+ char *test;
long tmp = strtol(param->value.c_str(), &test, 10);
if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX)
FormatFatalError("buffer size \"%s\" is not a "
@@ -279,14 +335,16 @@ initialize_decoder_and_player(void)
buffer_size *= 1024;
- buffered_chunks = buffer_size / CHUNK_SIZE;
+ const unsigned buffered_chunks = buffer_size / CHUNK_SIZE;
if (buffered_chunks >= 1 << 15)
FormatFatalError("buffer size \"%lu\" is too big",
(unsigned long)buffer_size);
+ float perc;
param = config_get_param(CONF_BUFFER_BEFORE_PLAY);
if (param != nullptr) {
+ char *test;
perc = strtod(param->value.c_str(), &test);
if (*test != '%' || perc < 0 || perc > 100) {
FormatFatalError("buffered before play \"%s\" is not "
@@ -297,7 +355,7 @@ initialize_decoder_and_player(void)
} else
perc = DEFAULT_BUFFER_BEFORE_PLAY;
- buffered_before_play = (perc / 100) * buffered_chunks;
+ unsigned buffered_before_play = (perc / 100) * buffered_chunks;
if (buffered_before_play > buffered_chunks)
buffered_before_play = buffered_chunks;
@@ -336,11 +394,13 @@ idle_event_emitted(void)
static void
shutdown_event_emitted(void)
{
- main_loop->Break();
+ instance->event_loop->Break();
}
#endif
+#ifndef ANDROID
+
int main(int argc, char *argv[])
{
#ifdef WIN32
@@ -350,14 +410,19 @@ int main(int argc, char *argv[])
#endif
}
+#endif
+
+static int mpd_main_after_fork(struct options);
+
+#ifdef ANDROID
+static inline
+#endif
int mpd_main(int argc, char *argv[])
{
struct options options;
- clock_t start;
- bool create_db;
Error error;
- bool success;
+#ifndef ANDROID
daemonize_close_stdin();
#ifdef HAVE_LOCALE_H
@@ -365,19 +430,43 @@ int mpd_main(int argc, char *argv[])
setlocale(LC_CTYPE,"");
#endif
+#ifdef HAVE_GLIB
g_set_application_name("Music Player Daemon");
#if !GLIB_CHECK_VERSION(2,32,0)
/* enable GLib's thread safety code */
g_thread_init(nullptr);
#endif
+#endif
+#endif
+
+ if (!IcuInit(error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
- io_thread_init();
winsock_init();
+ io_thread_init();
config_global_init();
- success = parse_cmdline(argc, argv, &options, error);
- if (!success) {
+#ifdef ANDROID
+ (void)argc;
+ (void)argv;
+
+ {
+ const auto sdcard = Environment::getExternalStorageDirectory();
+ if (!sdcard.IsNull()) {
+ const auto config_path =
+ AllocatedPath::Build(sdcard, "mpd.conf");
+ if (FileExists(config_path) &&
+ !ReadConfigFile(config_path, error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+#else
+ if (!parse_cmdline(argc, argv, &options, error)) {
LogError(error);
return EXIT_FAILURE;
}
@@ -386,6 +475,7 @@ int mpd_main(int argc, char *argv[])
LogError(error);
return EXIT_FAILURE;
}
+#endif
stats_global_init();
TagLoadConfig();
@@ -395,23 +485,60 @@ int mpd_main(int argc, char *argv[])
return EXIT_FAILURE;
}
- main_thread = ThreadId::GetCurrent();
- main_loop = new EventLoop(EventLoop::Default());
-
instance = new Instance();
+ instance->event_loop = new EventLoop();
+
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ instance->neighbors = new NeighborGlue();
+ if (!instance->neighbors->Init(io_thread_get(), *instance, error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+
+ if (instance->neighbors->IsEmpty()) {
+ delete instance->neighbors;
+ instance->neighbors = nullptr;
+ }
+#endif
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) {
+ initialize_decoder_and_player();
+
+ if (!listen_global_init(*instance->event_loop, *instance->partition,
+ error)) {
LogError(error);
return EXIT_FAILURE;
}
+#ifndef ANDROID
daemonize_set_user();
+ daemonize_begin(options.daemon);
+#endif
+
+#ifdef __BLOCKS__
+ /* Runs the OS X native event loop in the main thread, and runs
+ the rest of mpd_main on a new thread. This lets CoreAudio receive
+ route change notifications (e.g. plugging or unplugging headphones).
+ All hardware output on OS X ultimately uses CoreAudio internally.
+ This must be run after forking; if dispatch is called before forking,
+ the child process will have a broken internal dispatch state. */
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ exit(mpd_main_after_fork(options));
+ });
+ dispatch_main();
+ return EXIT_FAILURE; // unreachable, because dispatch_main never returns
+#else
+ return mpd_main_after_fork(options);
+#endif
+}
- GlobalEvents::Initialize(*main_loop);
+static int mpd_main_after_fork(struct options options)
+{
+ Error error;
+
+ GlobalEvents::Initialize(*instance->event_loop);
GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
#ifdef WIN32
GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted);
@@ -431,23 +558,23 @@ int mpd_main(int argc, char *argv[])
archive_plugin_init_all();
#endif
- if (!pcm_resample_global_init(error)) {
+ if (!pcm_convert_global_init(error)) {
LogError(error);
return EXIT_FAILURE;
}
decoder_plugin_init_all();
- update_global_init();
- create_db = !glue_db_init_and_load();
+#ifdef ENABLE_DATABASE
+ const bool create_db = InitDatabaseAndStorage();
+#endif
glue_sticker_init();
command_init();
- initialize_decoder_and_player();
- volume_init();
initAudioConfig();
- audio_output_all_init(instance->partition->pc);
+ instance->partition->outputs.Configure(*instance->event_loop,
+ instance->partition->pc);
client_manager_init();
replay_gain_global_init();
@@ -458,43 +585,59 @@ int mpd_main(int argc, char *argv[])
playlist_list_global_init();
- daemonize(options.daemon);
+#ifndef ANDROID
+ daemonize_commit();
setup_log_output(options.log_stderr);
- SignalHandlersInit(*main_loop);
+ SignalHandlersInit(*instance->event_loop);
+#endif
io_thread_start();
- ZeroconfInit(*main_loop);
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (instance->neighbors != nullptr &&
+ !instance->neighbors->Open(error))
+ FatalError(error);
+#endif
+
+ ZeroconfInit(*instance->event_loop);
- player_create(instance->partition->pc);
+ StartPlayerThread(instance->partition->pc);
+#ifdef ENABLE_DATABASE
if (create_db) {
/* the database failed to load: recreate the
database */
- unsigned job = update_enqueue("", true);
+ unsigned job = instance->update->Enqueue("", true);
if (job == 0)
FatalError("directory update failed");
}
+#endif
if (!glue_state_file_init(error)) {
- g_printerr("%s\n", error.GetMessage());
+ LogError(error);
return EXIT_FAILURE;
}
- audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(instance->partition->playlist.queue.random));
+ instance->partition->outputs.SetReplayGainMode(replay_gain_get_real_mode(instance->partition->playlist.queue.random));
- success = config_get_bool(CONF_AUTO_UPDATE, false);
+#ifdef ENABLE_DATABASE
+ if (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));
+ if (instance->storage != nullptr &&
+ instance->update != nullptr)
+ mpd_inotify_init(*instance->event_loop,
+ *instance->storage,
+ *instance->update,
+ config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
+ INT_MAX));
#else
- if (success)
FormatWarning(main_domain,
"inotify: auto_update was disabled. enable during compilation phase");
#endif
+ }
+#endif
config_global_check();
@@ -506,8 +649,12 @@ int mpd_main(int argc, char *argv[])
win32_app_started();
#endif
+ /* the MPD frontend does not care about timer slack; set it to
+ a huge value to allow the kernel to reduce CPU wakeups */
+ SetThreadTimerSlackMS(100);
+
/* run the main loop */
- main_loop->Run();
+ instance->event_loop->Run();
#ifdef WIN32
win32_app_stopping();
@@ -515,8 +662,11 @@ int mpd_main(int argc, char *argv[])
/* cleanup */
-#ifdef ENABLE_INOTIFY
+#if defined(ENABLE_DATABASE) && defined(ENABLE_INOTIFY)
mpd_inotify_finish();
+
+ if (instance->update != nullptr)
+ instance->update->CancelAllAsync();
#endif
if (state_file != nullptr) {
@@ -529,11 +679,23 @@ int mpd_main(int argc, char *argv[])
listen_global_finish();
delete instance->client_list;
- start = clock();
- DatabaseGlobalDeinit();
- FormatDebug(main_domain,
- "db_finish took %f seconds",
- ((float)(clock()-start))/CLOCKS_PER_SEC);
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (instance->neighbors != nullptr) {
+ instance->neighbors->Close();
+ delete instance->neighbors;
+ }
+#endif
+
+#ifdef ENABLE_DATABASE
+ delete instance->update;
+
+ if (instance->database != nullptr) {
+ instance->database->Close();
+ delete instance->database;
+ }
+
+ delete instance->storage;
+#endif
#ifdef ENABLE_SQLITE
sticker_global_finish();
@@ -543,27 +705,62 @@ int mpd_main(int argc, char *argv[])
playlist_list_global_finish();
input_stream_global_finish();
- audio_output_all_finish();
- volume_finish();
+
+#ifdef ENABLE_DATABASE
mapper_finish();
+#endif
+
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();
+#ifndef ANDROID
SignalHandlersFinish();
+#endif
+ delete instance->event_loop;
delete instance;
- delete main_loop;
+ instance = nullptr;
+#ifndef ANDROID
daemonize_finish();
+#endif
#ifdef WIN32
WSACleanup();
#endif
+ IcuFinish();
+
log_deinit();
return EXIT_SUCCESS;
}
+
+#ifdef ANDROID
+
+gcc_visibility_default
+JNIEXPORT void JNICALL
+Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context)
+{
+ Java::Init(env);
+ Java::File::Initialise(env);
+ Environment::Initialise(env);
+
+ context = new Context(env, _context);
+
+ mpd_main(0, nullptr);
+
+ delete context;
+ Environment::Deinitialise(env);
+}
+
+gcc_visibility_default
+JNIEXPORT void JNICALL
+Java_org_musicpd_Bridge_shutdown(JNIEnv *, jclass)
+{
+ if (instance != nullptr)
+ instance->event_loop->Break();
+}
+
+#endif
diff --git a/src/Main.hxx b/src/Main.hxx
index a8ec184c8..7e3fecd0b 100644
--- a/src/Main.hxx
+++ b/src/Main.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,16 +20,18 @@
#ifndef MPD_MAIN_HXX
#define MPD_MAIN_HXX
-class ThreadId;
class EventLoop;
+class Context;
struct Instance;
-extern ThreadId main_thread;
-
-extern EventLoop *main_loop;
+#ifdef ANDROID
+extern Context *context;
+#endif
extern Instance *instance;
+#ifndef ANDROID
+
/**
* A entry point for application.
* On non-Windows platforms this is called directly from main()
@@ -38,6 +40,8 @@ extern Instance *instance;
*/
int mpd_main(int argc, char *argv[]);
+#endif
+
#ifdef WIN32
/**
diff --git a/src/Mapper.cxx b/src/Mapper.cxx
index cbe45daa0..7baad9459 100644
--- a/src/Mapper.cxx
+++ b/src/Mapper.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,36 +23,18 @@
#include "config.h"
#include "Mapper.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/Charset.hxx"
-#include "fs/FileSystem.hxx"
-#include "fs/DirectoryReader.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
+#include "fs/CheckFile.hxx"
-#include <assert.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <dirent.h>
-
-static constexpr Domain mapper_domain("mapper");
-
-/**
- * The absolute path of the music directory encoded in UTF-8.
- */
-static std::string music_dir_utf8;
-static size_t music_dir_utf8_length;
+#ifdef ENABLE_DATABASE
+#include "storage/StorageInterface.hxx"
+#include "Instance.hxx"
+#include "Main.hxx"
+#endif
-/**
- * The absolute path of the music directory encoded in the filesystem
- * character set.
- */
-static AllocatedPath music_dir_fs = AllocatedPath::Null();
+#include <assert.h>
/**
* The absolute path of the playlist directory encoded in the
@@ -61,67 +43,18 @@ static AllocatedPath music_dir_fs = AllocatedPath::Null();
static AllocatedPath playlist_dir_fs = AllocatedPath::Null();
static void
-check_directory(const char *path_utf8, const AllocatedPath &path_fs)
-{
- struct stat st;
- if (!StatFile(path_fs, st)) {
- FormatErrno(mapper_domain,
- "Failed to stat directory \"%s\"",
- path_utf8);
- return;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- FormatError(mapper_domain,
- "Not a directory: %s", path_utf8);
- return;
- }
-
-#ifndef WIN32
- const auto x = AllocatedPath::Build(path_fs, ".");
- if (!StatFile(x, st) && errno == EACCES)
- FormatError(mapper_domain,
- "No permission to traverse (\"execute\") directory: %s",
- path_utf8);
-#endif
-
- const DirectoryReader reader(path_fs);
- if (reader.HasFailed() && errno == EACCES)
- FormatError(mapper_domain,
- "No permission to read directory: %s", path_utf8);
-}
-
-static void
-mapper_set_music_dir(AllocatedPath &&path)
-{
- assert(!path.IsNull());
-
- music_dir_fs = std::move(path);
- music_dir_fs.ChopSeparators();
-
- music_dir_utf8 = music_dir_fs.ToUTF8();
- music_dir_utf8_length = music_dir_utf8.length();
-
- check_directory(music_dir_utf8.c_str(), music_dir_fs);
-}
-
-static void
mapper_set_playlist_dir(AllocatedPath &&path)
{
assert(!path.IsNull());
playlist_dir_fs = std::move(path);
- const auto utf8 = playlist_dir_fs.ToUTF8();
- check_directory(utf8.c_str(), playlist_dir_fs);
+ CheckDirectoryReadable(playlist_dir_fs);
}
void
-mapper_init(AllocatedPath &&_music_dir, AllocatedPath &&_playlist_dir)
+mapper_init(AllocatedPath &&_playlist_dir)
{
- if (!_music_dir.IsNull())
- mapper_set_music_dir(std::move(_music_dir));
-
if (!_playlist_dir.IsNull())
mapper_set_playlist_dir(std::move(_playlist_dir));
}
@@ -130,30 +63,7 @@ void mapper_finish(void)
{
}
-const char *
-mapper_get_music_directory_utf8(void)
-{
- return music_dir_utf8.empty()
- ? nullptr
- : music_dir_utf8.c_str();
-}
-
-const AllocatedPath &
-mapper_get_music_directory_fs(void)
-{
- return music_dir_fs;
-}
-
-const char *
-map_to_relative_path(const char *path_utf8)
-{
- return !music_dir_utf8.empty() &&
- memcmp(path_utf8, music_dir_utf8.c_str(),
- music_dir_utf8_length) == 0 &&
- PathTraits::IsSeparatorUTF8(path_utf8[music_dir_utf8_length])
- ? path_utf8 + music_dir_utf8_length + 1
- : path_utf8;
-}
+#ifdef ENABLE_DATABASE
AllocatedPath
map_uri_fs(const char *uri)
@@ -161,6 +71,10 @@ map_uri_fs(const char *uri)
assert(uri != nullptr);
assert(*uri != '/');
+ if (instance->storage == nullptr)
+ return AllocatedPath::Null();
+
+ const auto music_dir_fs = instance->storage->MapFS("");
if (music_dir_fs.IsNull())
return AllocatedPath::Null();
@@ -171,70 +85,17 @@ map_uri_fs(const char *uri)
return AllocatedPath::Build(music_dir_fs, uri_fs);
}
-AllocatedPath
-map_directory_fs(const Directory &directory)
-{
- assert(!music_dir_fs.IsNull());
-
- if (directory.IsRoot())
- return music_dir_fs;
-
- return map_uri_fs(directory.GetPath());
-}
-
-AllocatedPath
-map_directory_child_fs(const Directory &directory, const char *name)
-{
- assert(!music_dir_fs.IsNull());
-
- /* check for invalid or unauthorized base names */
- if (*name == 0 || strchr(name, '/') != nullptr ||
- strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
- return AllocatedPath::Null();
-
- const auto parent_fs = map_directory_fs(directory);
- if (parent_fs.IsNull())
- return AllocatedPath::Null();
-
- const auto name_fs = AllocatedPath::FromUTF8(name);
- if (name_fs.IsNull())
- return AllocatedPath::Null();
-
- return AllocatedPath::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 AllocatedPath
-map_detached_song_fs(const char *uri_utf8)
-{
- auto uri_fs = AllocatedPath::FromUTF8(uri_utf8);
- if (uri_fs.IsNull())
- return uri_fs;
-
- return AllocatedPath::Build(music_dir_fs, uri_fs);
-}
-
-AllocatedPath
-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 AllocatedPath::FromUTF8(song.uri);
-}
-
std::string
map_fs_to_utf8(const char *path_fs)
{
- if (PathTraits::IsSeparatorFS(path_fs[0])) {
+ if (PathTraitsFS::IsSeparator(path_fs[0])) {
+ if (instance->storage == nullptr)
+ return std::string();
+
+ const auto music_dir_fs = instance->storage->MapFS("");
+ if (music_dir_fs.IsNull())
+ return std::string();
+
path_fs = music_dir_fs.RelativeFS(path_fs);
if (path_fs == nullptr || *path_fs == 0)
return std::string();
@@ -243,6 +104,8 @@ map_fs_to_utf8(const char *path_fs)
return PathToUTF8(path_fs);
}
+#endif
+
const AllocatedPath &
map_spl_path(void)
{
diff --git a/src/Mapper.hxx b/src/Mapper.hxx
index 947fd2822..7ff41f239 100644
--- a/src/Mapper.hxx
+++ b/src/Mapper.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,50 +30,14 @@
#define PLAYLIST_FILE_SUFFIX ".m3u"
-class Path;
class AllocatedPath;
-struct Directory;
-struct Song;
void
-mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir);
+mapper_init(AllocatedPath &&playlist_dir);
void mapper_finish(void);
-/**
- * Return the absolute path of the music directory encoded in UTF-8 or
- * nullptr if no music directory was configured.
- */
-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 AllocatedPath &
-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);
+#ifdef ENABLE_DATABASE
/**
* Determines the absolute file system path of a relative URI. This
@@ -85,39 +49,6 @@ AllocatedPath
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
-AllocatedPath
-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
-AllocatedPath
-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
-AllocatedPath
-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.
*
@@ -129,6 +60,8 @@ gcc_pure
std::string
map_fs_to_utf8(const char *path_fs);
+#endif
+
/**
* Returns the playlist directory.
*/
@@ -138,8 +71,7 @@ map_spl_path(void);
/**
* Maps a playlist name (without the ".m3u" suffix) to a file system
- * path. The return value is allocated on the heap and must be freed
- * with g_free().
+ * path.
*
* @return the path in file system encoding, or nullptr if mapping failed
*/
diff --git a/src/MemorySongEnumerator.cxx b/src/MemorySongEnumerator.cxx
deleted file mode 100644
index 7c9d05daa..000000000
--- a/src/MemorySongEnumerator.cxx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MemorySongEnumerator.hxx"
-
-Song *
-MemorySongEnumerator::NextSong()
-{
- if (songs.empty())
- return nullptr;
-
- auto result = songs.front().Steal();
- songs.pop_front();
- return result;
-}
diff --git a/src/MemorySongEnumerator.hxx b/src/MemorySongEnumerator.hxx
deleted file mode 100644
index 46086a064..000000000
--- a/src/MemorySongEnumerator.hxx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX
-#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
-
-#include "SongEnumerator.hxx"
-#include "SongPointer.hxx"
-
-#include <forward_list>
-
-class MemorySongEnumerator final : public SongEnumerator {
- std::forward_list<SongPointer> songs;
-
-public:
- MemorySongEnumerator(std::forward_list<SongPointer> &&_songs)
- :songs(std::move(_songs)) {}
-
- virtual Song *NextSong() override;
-};
-
-#endif
diff --git a/src/MixRampInfo.hxx b/src/MixRampInfo.hxx
index 9af41b77d..90c2c984a 100644
--- a/src/MixRampInfo.hxx
+++ b/src/MixRampInfo.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx
deleted file mode 100644
index 37225fd25..000000000
--- a/src/MixerAll.cxx
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MixerAll.hxx"
-#include "MixerControl.hxx"
-#include "MixerInternal.hxx"
-#include "MixerList.hxx"
-#include "OutputAll.hxx"
-#include "pcm/PcmVolume.hxx"
-#include "OutputInternal.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-static constexpr Domain mixer_domain("mixer");
-
-static int
-output_mixer_get_volume(unsigned i)
-{
- struct audio_output *output;
- int volume;
-
- assert(i < audio_output_count());
-
- output = audio_output_get(i);
- if (!output->enabled)
- return -1;
-
- Mixer *mixer = output->mixer;
- if (mixer == nullptr)
- return -1;
-
- Error error;
- volume = mixer_get_volume(mixer, error);
- if (volume < 0 && error.IsDefined())
- FormatError(error,
- "Failed to read mixer for '%s'",
- output->name);
-
- 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;
-
- assert(i < audio_output_count());
- assert(volume <= 100);
-
- output = audio_output_get(i);
- if (!output->enabled)
- return false;
-
- Mixer *mixer = output->mixer;
- if (mixer == nullptr)
- return false;
-
- Error error;
- success = mixer_set_volume(mixer, volume, error);
- if (!success && error.IsDefined())
- FormatError(error,
- "Failed to set mixer for '%s'",
- output->name);
-
- 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 == nullptr || !mixer->IsPlugin(software_mixer_plugin))
- return -1;
-
- return mixer_get_volume(mixer, IgnoreError());
-}
-
-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 != nullptr &&
- output->mixer->plugin == &software_mixer_plugin)
- mixer_set_volume(output->mixer, volume, IgnoreError());
- }
-}
diff --git a/src/MixerAll.hxx b/src/MixerAll.hxx
deleted file mode 100644
index fa7c89801..000000000
--- a/src/MixerAll.hxx
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Functions which affect the mixers of all audio outputs.
- */
-
-#ifndef MPD_MIXER_ALL_HXX
-#define MPD_MIXER_ALL_HXX
-
-#include "Compiler.h"
-
-/**
- * Returns the average volume of all available mixers (range 0..100).
- * Returns -1 if no mixer can be queried.
- */
-gcc_pure
-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.
- */
-gcc_pure
-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
deleted file mode 100644
index dd4f03543..000000000
--- a/src/MixerControl.cxx
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MixerControl.hxx"
-#include "MixerInternal.hxx"
-#include "util/Error.hxx"
-
-#include <assert.h>
-#include <stddef.h>
-
-Mixer *
-mixer_new(const struct mixer_plugin *plugin, void *ao,
- const config_param &param,
- Error &error)
-{
- Mixer *mixer;
-
- assert(plugin != nullptr);
-
- mixer = plugin->init(ao, param, error);
-
- assert(mixer == nullptr || mixer->IsPlugin(*plugin));
-
- return mixer;
-}
-
-void
-mixer_free(Mixer *mixer)
-{
- assert(mixer != nullptr);
- assert(mixer->plugin != nullptr);
-
- /* 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, Error &error)
-{
- bool success;
-
- assert(mixer != nullptr);
- assert(mixer->plugin != nullptr);
-
- const ScopeLock protect(mixer->mutex);
-
- if (mixer->open)
- success = true;
- else if (mixer->plugin->open == nullptr)
- success = mixer->open = true;
- else
- success = mixer->open = mixer->plugin->open(mixer, error);
-
- mixer->failed = !success;
-
- return success;
-}
-
-static void
-mixer_close_internal(Mixer *mixer)
-{
- assert(mixer != nullptr);
- assert(mixer->plugin != nullptr);
- assert(mixer->open);
-
- if (mixer->plugin->close != nullptr)
- mixer->plugin->close(mixer);
-
- mixer->open = false;
-}
-
-void
-mixer_close(Mixer *mixer)
-{
- assert(mixer != nullptr);
- assert(mixer->plugin != nullptr);
-
- 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, Error &error)
-{
- int volume;
-
- assert(mixer != nullptr);
-
- if (mixer->plugin->global && !mixer->failed &&
- !mixer_open(mixer, error))
- return -1;
-
- const ScopeLock protect(mixer->mutex);
-
- if (mixer->open) {
- volume = mixer->plugin->get_volume(mixer, error);
- if (volume < 0 && error.IsDefined())
- mixer_failed(mixer);
- } else
- volume = -1;
-
- return volume;
-}
-
-bool
-mixer_set_volume(Mixer *mixer, unsigned volume, Error &error)
-{
- assert(mixer != nullptr);
- assert(volume <= 100);
-
- if (mixer->plugin->global && !mixer->failed &&
- !mixer_open(mixer, error))
- return false;
-
- const ScopeLock protect(mixer->mutex);
-
- return mixer->open &&
- mixer->plugin->set_volume(mixer, volume, error);
-}
diff --git a/src/MixerControl.hxx b/src/MixerControl.hxx
deleted file mode 100644
index c1ee01eec..000000000
--- a/src/MixerControl.hxx
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Functions which manipulate a #mixer object.
- */
-
-#ifndef MPD_MIXER_CONTROL_HXX
-#define MPD_MIXER_CONTROL_HXX
-
-class Error;
-class Mixer;
-struct mixer_plugin;
-struct config_param;
-
-Mixer *
-mixer_new(const struct mixer_plugin *plugin, void *ao,
- const config_param &param,
- Error &error);
-
-void
-mixer_free(Mixer *mixer);
-
-bool
-mixer_open(Mixer *mixer, Error &error);
-
-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, Error &error);
-
-bool
-mixer_set_volume(Mixer *mixer, unsigned volume, Error &error);
-
-#endif
diff --git a/src/MixerInternal.hxx b/src/MixerInternal.hxx
deleted file mode 100644
index e421a34b4..000000000
--- a/src/MixerInternal.hxx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 440f442ba..000000000
--- a/src/MixerList.hxx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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
deleted file mode 100644
index db6fb2471..000000000
--- a/src/MixerPlugin.hxx
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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
-
-struct config_param;
-class Mixer;
-class Error;
-
-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
- * nullptr to ignore errors
- * @return a mixer object, or nullptr on error
- */
- Mixer *(*init)(void *ao, const config_param &param,
- Error &error);
-
- /**
- * Finish and free mixer data
- */
- void (*finish)(Mixer *data);
-
- /**
- * Open mixer device
- *
- * @param error_r location to store the error occurring, or
- * nullptr to ignore errors
- * @return true on success, false on error
- */
- bool (*open)(Mixer *data, Error &error);
-
- /**
- * Close mixer device
- */
- void (*close)(Mixer *data);
-
- /**
- * Reads the current volume.
- *
- * @param error_r location to store the error occurring, or
- * nullptr to ignore errors
- * @return the current volume (0..100 including) or -1 if
- * unavailable or on error (error set, mixer will be closed)
- */
- int (*get_volume)(Mixer *mixer, Error &error);
-
- /**
- * Sets the volume.
- *
- * @param error_r location to store the error occurring, or
- * nullptr 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,
- Error &error);
-
- /**
- * 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
deleted file mode 100644
index 435079790..000000000
--- a/src/MixerType.cxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
deleted file mode 100644
index 320a36c04..000000000
--- a/src/MixerType.hxx
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
index c811d8627..709b40413 100644
--- a/src/MusicBuffer.cxx
+++ b/src/MusicBuffer.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,7 +30,7 @@ MusicBuffer::MusicBuffer(unsigned num_chunks)
FatalError("Failed to allocate buffer");
}
-music_chunk *
+MusicChunk *
MusicBuffer::Allocate()
{
const ScopeLock protect(mutex);
@@ -38,7 +38,7 @@ MusicBuffer::Allocate()
}
void
-MusicBuffer::Return(music_chunk *chunk)
+MusicBuffer::Return(MusicChunk *chunk)
{
assert(chunk != nullptr);
diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx
index d2b23d43a..cf7c90f91 100644
--- a/src/MusicBuffer.hxx
+++ b/src/MusicBuffer.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,22 +23,22 @@
#include "util/SliceBuffer.hxx"
#include "thread/Mutex.hxx"
-struct music_chunk;
+struct MusicChunk;
/**
- * An allocator for #music_chunk objects.
+ * An allocator for #MusicChunk objects.
*/
class MusicBuffer {
/** a mutex which protects #buffer */
Mutex mutex;
- SliceBuffer<music_chunk> buffer;
+ SliceBuffer<MusicChunk> buffer;
public:
/**
* Creates a new #MusicBuffer object.
*
- * @param num_chunks the number of #music_chunk reserved in
+ * @param num_chunks the number of #MusicChunk reserved in
* this buffer
*/
MusicBuffer(unsigned num_chunks);
@@ -71,13 +71,13 @@ public:
* @return an empty chunk or nullptr if there are no chunks
* available
*/
- music_chunk *Allocate();
+ MusicChunk *Allocate();
/**
* Returns a chunk to the buffer. It can be reused by
* Allocate() then.
*/
- void Return(music_chunk *chunk);
+ void Return(MusicChunk *chunk);
};
#endif
diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx
index 2d20ac7ac..3cfd232c0 100644
--- a/src/MusicChunk.cxx
+++ b/src/MusicChunk.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,14 +24,14 @@
#include <assert.h>
-music_chunk::~music_chunk()
+MusicChunk::~MusicChunk()
{
delete tag;
}
#ifndef NDEBUG
bool
-music_chunk::CheckFormat(const AudioFormat other_format) const
+MusicChunk::CheckFormat(const AudioFormat other_format) const
{
assert(other_format.IsValid());
@@ -40,34 +40,31 @@ music_chunk::CheckFormat(const AudioFormat other_format) const
#endif
WritableBuffer<void>
-music_chunk::Write(const AudioFormat af,
- float data_time, uint16_t _bit_rate)
+MusicChunk::Write(const AudioFormat af,
+ SongTime data_time, uint16_t _bit_rate)
{
assert(CheckFormat(af));
assert(length == 0 || audio_format.IsValid());
if (length == 0) {
/* if the chunk is empty, nobody has set bitRate and
- times yet */
+ time 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 WritableBuffer<void>::Null();
+ time = data_time;
#ifndef NDEBUG
- audio_format = af;
+ audio_format = af;
#endif
+ }
+ const size_t frame_size = af.GetFrameSize();
+ size_t num_frames = (sizeof(data) - length) / frame_size;
return { data + length, num_frames * frame_size };
}
bool
-music_chunk::Expand(const AudioFormat af, size_t _length)
+MusicChunk::Expand(const AudioFormat af, size_t _length)
{
const size_t frame_size = af.GetFrameSize();
diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx
index ecd57090b..805112d02 100644
--- a/src/MusicChunk.hxx
+++ b/src/MusicChunk.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#ifndef MPD_MUSIC_CHUNK_HXX
#define MPD_MUSIC_CHUNK_HXX
+#include "Chrono.hxx"
#include "ReplayGainInfo.hxx"
#include "util/WritableBuffer.hxx"
@@ -39,15 +40,15 @@ struct Tag;
* A chunk of music data. Its format is defined by the
* MusicPipe::Push() caller.
*/
-struct music_chunk {
+struct MusicChunk {
/** the next chunk in a linked list */
- struct music_chunk *next;
+ MusicChunk *next;
/**
* An optional chunk which should be mixed into this chunk.
* This is used for cross-fading.
*/
- struct music_chunk *other;
+ MusicChunk *other;
/**
* The current mix ratio for cross-fading: 1.0 means play 100%
@@ -62,7 +63,7 @@ struct music_chunk {
uint16_t bit_rate;
/** the time stamp within the song */
- float times;
+ SignedSongTime time;
/**
* An optional tag associated with this chunk (and the
@@ -92,13 +93,13 @@ struct music_chunk {
AudioFormat audio_format;
#endif
- music_chunk()
+ MusicChunk()
:other(nullptr),
length(0),
tag(nullptr),
replay_gain_serial(0) {}
- ~music_chunk();
+ ~MusicChunk();
bool IsEmpty() const {
return length == 0 && tag == nullptr;
@@ -116,9 +117,9 @@ struct music_chunk {
/**
* Prepares appending to the music chunk. Returns a buffer
* where you may write into. After you are finished, call
- * music_chunk_expand().
+ * Expand().
*
- * @param chunk the music_chunk object
+ * @param chunk the MusicChunk 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
@@ -128,13 +129,14 @@ struct music_chunk {
* @return a writable buffer, or nullptr if the chunk is full
*/
WritableBuffer<void> Write(AudioFormat af,
- float data_time, uint16_t bit_rate);
+ SongTime data_time,
+ uint16_t bit_rate);
/**
* Increases the length of the chunk after the caller has written to
- * the buffer returned by music_chunk_write().
+ * the buffer returned by Write().
*
- * @param chunk the music_chunk object
+ * @param chunk the MusicChunk 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
diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx
index a5bbe590e..43ce2dbb2 100644
--- a/src/MusicPipe.cxx
+++ b/src/MusicPipe.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,11 +25,11 @@
#ifndef NDEBUG
bool
-MusicPipe::Contains(const music_chunk *chunk) const
+MusicPipe::Contains(const MusicChunk *chunk) const
{
const ScopeLock protect(mutex);
- for (const struct music_chunk *i = head; i != nullptr; i = i->next)
+ for (const MusicChunk *i = head; i != nullptr; i = i->next)
if (i == chunk)
return true;
@@ -38,12 +38,12 @@ MusicPipe::Contains(const music_chunk *chunk) const
#endif
-music_chunk *
+MusicChunk *
MusicPipe::Shift()
{
const ScopeLock protect(mutex);
- music_chunk *chunk = head;
+ MusicChunk *chunk = head;
if (chunk != nullptr) {
assert(!chunk->IsEmpty());
@@ -62,7 +62,7 @@ MusicPipe::Shift()
#ifndef NDEBUG
/* poison the "next" reference */
- chunk->next = (music_chunk *)(void *)0x01010101;
+ chunk->next = (MusicChunk *)(void *)0x01010101;
if (size == 0)
audio_format.Clear();
@@ -75,14 +75,14 @@ MusicPipe::Shift()
void
MusicPipe::Clear(MusicBuffer &buffer)
{
- music_chunk *chunk;
+ MusicChunk *chunk;
while ((chunk = Shift()) != nullptr)
buffer.Return(chunk);
}
void
-MusicPipe::Push(music_chunk *chunk)
+MusicPipe::Push(MusicChunk *chunk)
{
assert(!chunk->IsEmpty());
assert(chunk->length == 0 || chunk->audio_format.IsValid());
diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx
index f2db33cc5..4f29d0728 100644
--- a/src/MusicPipe.hxx
+++ b/src/MusicPipe.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,19 +29,19 @@
#include <assert.h>
-struct music_chunk;
+struct MusicChunk;
class MusicBuffer;
/**
- * A queue of #music_chunk objects. One party appends chunks at the
+ * A queue of #MusicChunk objects. One party appends chunks at the
* tail, and the other consumes them from the head.
*/
class MusicPipe {
/** the first chunk */
- music_chunk *head;
+ MusicChunk *head;
/** a pointer to the tail of the chunk */
- music_chunk **tail_r;
+ MusicChunk **tail_r;
/** the current number of chunks */
unsigned size;
@@ -87,22 +87,22 @@ public:
* Checks if the specified chunk is enqueued in the music pipe.
*/
gcc_pure
- bool Contains(const music_chunk *chunk) const;
+ bool Contains(const MusicChunk *chunk) const;
#endif
/**
- * Returns the first #music_chunk from the pipe. Returns
+ * Returns the first #MusicChunk from the pipe. Returns
* nullptr if the pipe is empty.
*/
gcc_pure
- const music_chunk *Peek() const {
+ const MusicChunk *Peek() const {
return head;
}
/**
* Removes the first chunk from the head, and returns it.
*/
- music_chunk *Shift();
+ MusicChunk *Shift();
/**
* Clears the whole pipe and returns the chunks to the buffer.
@@ -114,7 +114,7 @@ public:
/**
* Pushes a chunk to the tail of the pipe.
*/
- void Push(music_chunk *chunk);
+ void Push(MusicChunk *chunk);
/**
* Returns the number of chunks currently in this pipe.
diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx
deleted file mode 100644
index e905fd9db..000000000
--- a/src/OutputAPI.hxx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_API_HXX
-#define MPD_OUTPUT_API_HXX
-
-#include "OutputPlugin.hxx"
-#include "OutputInternal.hxx"
-#include "AudioFormat.hxx"
-#include "tag/Tag.hxx"
-#include "ConfigData.hxx"
-
-#endif
diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx
deleted file mode 100644
index 36d41184a..000000000
--- a/src/OutputAll.cxx
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OutputAll.hxx"
-#include "PlayerControl.hxx"
-#include "OutputInternal.hxx"
-#include "OutputControl.hxx"
-#include "OutputError.hxx"
-#include "MusicBuffer.hxx"
-#include "MusicPipe.hxx"
-#include "MusicChunk.hxx"
-#include "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "notify.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static AudioFormat input_audio_format;
-
-static struct audio_output **audio_outputs;
-static unsigned int num_audio_outputs;
-
-/**
- * The #MusicBuffer object where consumed chunks are returned.
- */
-static MusicBuffer *g_music_buffer;
-
-/**
- * The #MusicPipe object which feeds all audio outputs. It is filled
- * by audio_output_all_play().
- */
-static MusicPipe *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] != nullptr);
-
- 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 nullptr;
-}
-
-gcc_const
-static unsigned
-audio_output_config_count(void)
-{
- unsigned int nr = 0;
- const struct config_param *param = nullptr;
-
- 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(PlayerControl &pc)
-{
- const struct config_param *param = nullptr;
- unsigned int i;
- Error error;
-
- 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;
- }
-
- audio_output *output = audio_output_new(*param, pc, error);
- if (output == nullptr) {
- if (param != nullptr)
- FormatFatalError("line %i: %s",
- param->line,
- error.GetMessage());
- else
- FatalError(error);
- }
-
- audio_outputs[i] = output;
-
- /* require output names to be unique: */
- for (j = 0; j < i; j++) {
- if (!strcmp(output->name, audio_outputs[j]->name)) {
- FormatFatalError("output devices with identical "
- "names: %s", 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 = nullptr;
- 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 != nullptr) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
-}
-
-/**
- * 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(ReplayGainMode 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, Error &error)
-{
- bool ret;
- unsigned int i;
-
- assert(g_music_buffer != nullptr);
- assert(g_mp != nullptr);
- assert(chunk != nullptr);
- assert(chunk->CheckFormat(input_audio_format));
-
- ret = audio_output_all_update();
- if (!ret) {
- /* TODO: obtain real error */
- error.Set(output_domain, "Failed to open audio output");
- return false;
- }
-
- g_mp->Push(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,
- MusicBuffer &buffer,
- Error &error)
-{
- bool ret = false, enabled = false;
- unsigned int i;
-
- assert(g_music_buffer == nullptr || g_music_buffer == &buffer);
- assert((g_mp == nullptr) == (g_music_buffer == nullptr));
-
- g_music_buffer = &buffer;
-
- /* the audio format must be the same as existing chunks in the
- pipe */
- assert(g_mp == nullptr || g_mp->CheckFormat(audio_format));
-
- if (g_mp == nullptr)
- g_mp = new MusicPipe();
- else
- /* if the pipe hasn't been cleared, the the audio
- format must not have changed */
- assert(g_mp->IsEmpty() || 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)
- error.Set(output_domain, "All audio outputs are disabled");
- else if (!ret)
- /* TODO: obtain real error */
- error.Set(output_domain, "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 == nullptr)
- return false;
-
- assert(chunk == ao->chunk || g_mp->Contains(ao->chunk));
-
- if (chunk != ao->chunk) {
- assert(chunk->next != nullptr);
- return true;
- }
-
- return ao->chunk_finished && chunk->next == nullptr;
-}
-
-/**
- * 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(gcc_unused const struct music_chunk *chunk, bool *locked)
-{
- assert(chunk->next == nullptr);
- assert(g_mp->Contains(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 = nullptr;
- }
-}
-
-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 != nullptr);
- assert(g_mp != nullptr);
-
- while ((chunk = g_mp->Peek()) != nullptr) {
- assert(!g_mp->IsEmpty());
-
- if (!chunk_is_consumed(chunk))
- /* at least one output is not finished playing
- this chunk */
- return g_mp->GetSize();
-
- 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 == nullptr;
- 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 = g_mp->Shift();
- 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 */
- g_music_buffer->Return(shifted);
- }
-
- return 0;
-}
-
-bool
-audio_output_all_wait(PlayerControl &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 != nullptr)
- g_mp->Clear(*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 != nullptr) {
- assert(g_music_buffer != nullptr);
-
- g_mp->Clear(*g_music_buffer);
- delete g_mp;
- g_mp = nullptr;
- }
-
- g_music_buffer = nullptr;
-
- 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 != nullptr) {
- assert(g_music_buffer != nullptr);
-
- g_mp->Clear(*g_music_buffer);
- delete g_mp;
- g_mp = nullptr;
- }
-
- g_music_buffer = nullptr;
-
- 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
deleted file mode 100644
index 98061c345..000000000
--- a/src/OutputAll.hxx
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Functions for dealing with all configured (enabled) audion outputs
- * at once.
- *
- */
-
-#ifndef OUTPUT_ALL_H
-#define OUTPUT_ALL_H
-
-#include "ReplayGainInfo.hxx"
-#include "Compiler.h"
-
-struct AudioFormat;
-class MusicBuffer;
-struct music_chunk;
-struct PlayerControl;
-class Error;
-
-/**
- * Global initialization: load audio outputs from the configuration
- * file and initialize them.
- */
-void
-audio_output_all_init(PlayerControl &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.
- */
-gcc_const
-unsigned int audio_output_count(void);
-
-/**
- * Returns the "i"th audio output device.
- */
-gcc_const
-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.
- */
-gcc_pure
-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,
- MusicBuffer &buffer,
- Error &error);
-
-/**
- * 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(ReplayGainMode mode);
-
-/**
- * Enqueue a #music_chunk object for playing, i.e. pushes it to a
- * #MusicPipe.
- *
- * @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, Error &error);
-
-/**
- * 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 #MusicPipe
- */
-unsigned
-audio_output_all_check(void);
-
-/**
- * Checks if the size of the #MusicPipe 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(PlayerControl &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.
- */
-gcc_pure
-float
-audio_output_all_get_elapsed_time(void);
-
-#endif
diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx
deleted file mode 100644
index 10b5bb322..000000000
--- a/src/OutputCommand.cxx
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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 != nullptr) {
- mixer_close(mixer);
- idle_add(IDLE_MIXER);
- }
-
- ao->player_control->UpdateAudio();
-
- ++audio_output_state_version;
-
- return true;
-}
-
-bool
-audio_output_toggle_index(unsigned idx)
-{
- struct audio_output *ao;
-
- if (idx >= audio_output_count())
- return false;
-
- ao = audio_output_get(idx);
- const bool enabled = ao->enabled = !ao->enabled;
- idle_add(IDLE_OUTPUT);
-
- if (!enabled) {
- Mixer *mixer = ao->mixer;
- if (mixer != nullptr) {
- 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
deleted file mode 100644
index 46fab92c5..000000000
--- a/src/OutputCommand.hxx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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);
-
-/**
- * Toggles an audio output. Returns false if the specified output
- * does not exist.
- */
-bool
-audio_output_toggle_index(unsigned idx);
-
-#endif
diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx
deleted file mode 100644
index 27f280231..000000000
--- a/src/OutputControl.cxx
+++ /dev/null
@@ -1,336 +0,0 @@
-
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OutputControl.hxx"
-#include "OutputThread.hxx"
-#include "OutputInternal.hxx"
-#include "OutputPlugin.hxx"
-#include "OutputError.hxx"
-#include "MixerPlugin.hxx"
-#include "MixerControl.hxx"
-#include "notify.hxx"
-#include "filter/ReplayGainFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-/** after a failure, wait this number of seconds before
- automatically reopening the device */
-static constexpr unsigned 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,
- ReplayGainMode mode)
-{
- if (ao->replay_gain_filter != nullptr)
- replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
- if (ao->other_replay_gain_filter != nullptr)
- replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode);
-}
-
-void
-audio_output_enable(struct audio_output *ao)
-{
- if (!ao->thread.IsDefined()) {
- if (ao->plugin->enable == nullptr) {
- /* 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.IsDefined()) {
- if (ao->plugin->disable == nullptr)
- 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 MusicPipe &mp)
-{
- bool open;
-
- assert(ao != nullptr);
- assert(ao->allow_play);
- assert(audio_format.IsValid());
-
- if (ao->fail_timer != nullptr) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
-
- if (ao->open && audio_format == ao->in_audio_format) {
- assert(ao->pipe == &mp ||
- (ao->always_on && ao->pause));
-
- if (ao->pause) {
- ao->chunk = nullptr;
- 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 = nullptr;
-
- ao->pipe = &mp;
-
- if (!ao->thread.IsDefined())
- audio_output_thread_start(ao);
-
- ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
- open = ao->open;
-
- if (open && ao->mixer != nullptr) {
- Error error;
- if (!mixer_open(ao->mixer, error))
- FormatWarning(output_domain,
- "Failed to open mixer for '%s'",
- ao->name);
- }
-
- 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 != nullptr);
- assert(ao->allow_play);
-
- if (ao->mixer != nullptr)
- mixer_auto_close(ao->mixer);
-
- assert(!ao->open || ao->fail_timer == nullptr);
-
- if (ao->open)
- ao_command(ao, AO_COMMAND_CLOSE);
- else if (ao->fail_timer != nullptr) {
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
-}
-
-bool
-audio_output_update(struct audio_output *ao,
- const AudioFormat audio_format,
- const MusicPipe &mp)
-{
- const ScopeLock protect(ao->mutex);
-
- if (ao->enabled && ao->really_enabled) {
- if (ao->fail_timer == nullptr ||
- g_timer_elapsed(ao->fail_timer, nullptr) > REOPEN_AFTER) {
- 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->in_playback_loop &&
- !ao->woken_for_play) {
- ao->woken_for_play = true;
- ao->cond.signal();
- }
-}
-
-void audio_output_pause(struct audio_output *ao)
-{
- if (ao->mixer != nullptr && ao->plugin->pause == nullptr)
- /* 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 != nullptr);
- assert(!ao->open || ao->fail_timer == nullptr);
-
- 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 == nullptr);
-
- if (ao->thread.IsDefined()) {
- assert(ao->allow_play);
- ao_lock_command(ao, AO_COMMAND_KILL);
- ao->thread.Join();
- }
-
- audio_output_free(ao);
-}
diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx
deleted file mode 100644
index d8f0b432d..000000000
--- a/src/OutputControl.hxx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_CONTROL_HXX
-#define MPD_OUTPUT_CONTROL_HXX
-
-#include "ReplayGainInfo.hxx"
-
-#include <stddef.h>
-
-struct audio_output;
-struct AudioFormat;
-struct config_param;
-class MusicPipe;
-
-void
-audio_output_set_replay_gain_mode(struct audio_output *ao,
- ReplayGainMode 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 MusicPipe &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.cxx b/src/OutputError.cxx
deleted file mode 100644
index a18bfff23..000000000
--- a/src/OutputError.cxx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "OutputError.hxx"
-#include "util/Domain.hxx"
-
-const Domain output_domain("output");
diff --git a/src/OutputError.hxx b/src/OutputError.hxx
deleted file mode 100644
index 22a11fdbd..000000000
--- a/src/OutputError.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_ERROR_HXX
-#define MPD_OUTPUT_ERROR_HXX
-
-extern const class Domain output_domain;
-
-#endif
diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx
deleted file mode 100644
index db6599b53..000000000
--- a/src/OutputFinish.cxx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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 == nullptr);
- assert(!ao->thread.IsDefined());
-
- if (ao->mixer != nullptr)
- 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 == nullptr);
- assert(!ao->thread.IsDefined());
-
- ao_plugin_finish(ao);
-}
diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx
deleted file mode 100644
index 28eba1ab2..000000000
--- a/src/OutputInit.cxx
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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 "ConfigError.hxx"
-#include "ConfigGlobal.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-#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(Error &error)
-{
- LogDefault(output_domain, "Attempt to detect audio output device");
-
- audio_output_plugins_for_each(plugin) {
- if (plugin->test_default_device == nullptr)
- continue;
-
- FormatDefault(output_domain,
- "Attempting to detect a %s audio device",
- plugin->name);
- if (ao_plugin_test_default_device(plugin))
- return plugin;
- }
-
- error.Set(output_domain, "Unable to detect an audio device");
- return nullptr;
-}
-
-/**
- * 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 != nullptr)
- 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,
- Error &error)
-{
- Mixer *mixer;
-
- switch (audio_output_mixer_type(param)) {
- case MIXER_TYPE_NONE:
- case MIXER_TYPE_UNKNOWN:
- return nullptr;
-
- case MIXER_TYPE_HARDWARE:
- if (plugin == nullptr)
- return nullptr;
-
- return mixer_new(plugin, ao, param, error);
-
- case MIXER_TYPE_SOFTWARE:
- mixer = mixer_new(&software_mixer_plugin, nullptr,
- config_param(),
- IgnoreError());
- assert(mixer != nullptr);
-
- 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, Error &error)
-{
- assert(ao != nullptr);
- assert(plugin != nullptr);
- assert(plugin->finish != nullptr);
- assert(plugin->open != nullptr);
- assert(plugin->close != nullptr);
- assert(plugin->play != nullptr);
-
- if (!param.IsNull()) {
- ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
- if (ao->name == nullptr) {
- error.Set(config_domain,
- "Missing \"name\" configuration");
- return false;
- }
-
- const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
- if (p != nullptr) {
- bool success =
- audio_format_parse(ao->config_audio_format,
- p, true, error);
- 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->in_playback_loop = false;
- ao->woken_for_play = false;
- ao->fail_timer = nullptr;
-
- /* set up the filter chain */
-
- ao->filter = filter_chain_new();
- assert(ao->filter != nullptr);
-
- /* create the normalization filter (if configured) */
-
- if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
- Filter *normalize_filter =
- filter_new(&normalize_filter_plugin, config_param(),
- IgnoreError());
- assert(normalize_filter != nullptr);
-
- filter_chain_append(*ao->filter, "normalize",
- autoconvert_filter_new(normalize_filter));
- }
-
- Error filter_error;
- filter_chain_parse(*ao->filter,
- param.GetBlockValue(AUDIO_FILTERS, ""),
- filter_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 (filter_error.IsDefined())
- FormatError(filter_error,
- "Failed to initialize filter chain for '%s'",
- ao->name);
-
- ao->command = AO_COMMAND_NONE;
-
- ao->mixer = nullptr;
- ao->replay_gain_filter = nullptr;
- ao->other_replay_gain_filter = nullptr;
-
- /* done */
-
- return true;
-}
-
-static bool
-audio_output_setup(struct audio_output *ao, const config_param &param,
- Error &error)
-{
-
- /* 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, IgnoreError());
- assert(ao->replay_gain_filter != nullptr);
-
- ao->replay_gain_serial = 0;
-
- ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
- param,
- IgnoreError());
- assert(ao->other_replay_gain_filter != nullptr);
-
- ao->other_replay_gain_serial = 0;
- } else {
- ao->replay_gain_filter = nullptr;
- ao->other_replay_gain_filter = nullptr;
- }
-
- /* set up the mixer */
-
- Error mixer_error;
- ao->mixer = audio_output_load_mixer(ao, param,
- ao->plugin->mixer_plugin,
- *ao->filter, mixer_error);
- if (ao->mixer == nullptr && mixer_error.IsDefined())
- FormatError(mixer_error,
- "Failed to initialize hardware mixer for '%s'",
- ao->name);
-
- /* use the hardware mixer for replay gain? */
-
- if (strcmp(replay_gain_handler, "mixer") == 0) {
- if (ao->mixer != nullptr)
- replay_gain_filter_set_mixer(ao->replay_gain_filter,
- ao->mixer, 100);
- else
- FormatError(output_domain,
- "No such mixer for output '%s'", ao->name);
- } else if (strcmp(replay_gain_handler, "software") != 0 &&
- ao->replay_gain_filter != nullptr) {
- error.Set(config_domain,
- "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(),
- IgnoreError());
- assert(ao->convert_filter != nullptr);
-
- filter_chain_append(*ao->filter, "convert", ao->convert_filter);
-
- return true;
-}
-
-struct audio_output *
-audio_output_new(const config_param &param,
- PlayerControl &pc,
- Error &error)
-{
- const struct audio_output_plugin *plugin;
-
- if (!param.IsNull()) {
- const char *p;
-
- p = param.GetBlockValue(AUDIO_OUTPUT_TYPE);
- if (p == nullptr) {
- error.Set(config_domain,
- "Missing \"type\" configuration");
- return nullptr;
- }
-
- plugin = audio_output_plugin_get(p);
- if (plugin == nullptr) {
- error.Format(config_domain,
- "No such audio output plugin: %s", p);
- return nullptr;
- }
- } else {
- LogWarning(output_domain,
- "No 'audio_output' defined in config file");
-
- plugin = audio_output_detect(error);
- if (plugin == nullptr)
- return nullptr;
-
- FormatDefault(output_domain,
- "Successfully detected a %s audio device",
- plugin->name);
- }
-
- struct audio_output *ao = ao_plugin_init(plugin, param, error);
- if (ao == nullptr)
- return nullptr;
-
- if (!audio_output_setup(ao, param, error)) {
- ao_plugin_finish(ao);
- return nullptr;
- }
-
- ao->player_control = &pc;
- return ao;
-}
diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx
deleted file mode 100644
index c07cdf856..000000000
--- a/src/OutputInternal.hxx
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_INTERNAL_HXX
-#define MPD_OUTPUT_INTERNAL_HXX
-
-#include "AudioFormat.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "thread/Thread.hxx"
-
-#include <time.h>
-
-class Error;
-class Filter;
-class MusicPipe;
-struct config_param;
-struct PlayerControl;
-typedef struct _GTimer GTimer;
-
-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 nullptr 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;
-
- /**
- * True while the OutputThread is inside ao_play(). This
- * means the PlayerThread does not need to wake up the
- * OutputThread when new chunks are added to the MusicPipe,
- * because the OutputThread is already watching that.
- */
- bool in_playback_loop;
-
- /**
- * Has the OutputThread been woken up to play more chunks?
- * This is set by audio_output_play() and reset by ao_play()
- * to reduce the number of duplicate wakeups.
- */
- bool woken_for_play;
-
- /**
- * If not nullptr, 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 nullptr if the output thread isn't
- * running.
- */
- Thread 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 MusicPipe *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 PlayerControl object which "owns" this output. This
- * object is needed to signal command completion.
- */
- PlayerControl *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,
- PlayerControl &pc,
- Error &error);
-
-bool
-ao_base_init(struct audio_output *ao,
- const struct audio_output_plugin *plugin,
- const config_param &param, Error &error);
-
-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
deleted file mode 100644
index b569408dc..000000000
--- a/src/OutputList.cxx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#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
- nullptr
-};
-
-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 nullptr;
-}
diff --git a/src/OutputList.hxx b/src/OutputList.hxx
deleted file mode 100644
index 756cf22a4..000000000
--- a/src/OutputList.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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) != nullptr; ++output_plugin_iterator)
-
-#endif
diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx
deleted file mode 100644
index 31d9cce96..000000000
--- a/src/OutputPlugin.cxx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OutputPlugin.hxx"
-#include "OutputInternal.hxx"
-
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
- const config_param &param,
- Error &error)
-{
- assert(plugin != nullptr);
- assert(plugin->init != nullptr);
-
- 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, Error &error_r)
-{
- return ao->plugin->enable != nullptr
- ? ao->plugin->enable(ao, error_r)
- : true;
-}
-
-void
-ao_plugin_disable(struct audio_output *ao)
-{
- if (ao->plugin->disable != nullptr)
- ao->plugin->disable(ao);
-}
-
-bool
-ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &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 != nullptr
- ? ao->plugin->delay(ao)
- : 0;
-}
-
-void
-ao_plugin_send_tag(struct audio_output *ao, const Tag *tag)
-{
- if (ao->plugin->send_tag != nullptr)
- ao->plugin->send_tag(ao, tag);
-}
-
-size_t
-ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- return ao->plugin->play(ao, chunk, size, error);
-}
-
-void
-ao_plugin_drain(struct audio_output *ao)
-{
- if (ao->plugin->drain != nullptr)
- ao->plugin->drain(ao);
-}
-
-void
-ao_plugin_cancel(struct audio_output *ao)
-{
- if (ao->plugin->cancel != nullptr)
- ao->plugin->cancel(ao);
-}
-
-bool
-ao_plugin_pause(struct audio_output *ao)
-{
- return ao->plugin->pause != nullptr && ao->plugin->pause(ao);
-}
diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx
deleted file mode 100644
index b9f4b7dfb..000000000
--- a/src/OutputPlugin.hxx
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_PLUGIN_HXX
-#define MPD_OUTPUT_PLUGIN_HXX
-
-#include "Compiler.h"
-
-#include <stddef.h>
-
-struct config_param;
-struct AudioFormat;
-struct Tag;
-class Error;
-
-/**
- * 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 nullptr if there is
- * no configuration
- * @return nullptr on error, or an opaque pointer to the plugin's
- * data
- */
- struct audio_output *(*init)(const config_param &param,
- Error &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.
- *
- * @return true on success, false on error
- */
- bool (*enable)(struct audio_output *data, Error &error);
-
- /**
- * 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
- */
- bool (*open)(struct audio_output *data, AudioFormat &audio_format,
- Error &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.
- *
- * @return the number of bytes played, or 0 on error
- */
- size_t (*play)(struct audio_output *data,
- const void *chunk, size_t size,
- Error &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 nullptr 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 != nullptr
- ? plugin->test_default_device()
- : false;
-}
-
-gcc_malloc
-struct audio_output *
-ao_plugin_init(const struct audio_output_plugin *plugin,
- const config_param &param,
- Error &error);
-
-void
-ao_plugin_finish(struct audio_output *ao);
-
-bool
-ao_plugin_enable(struct audio_output *ao, Error &error);
-
-void
-ao_plugin_disable(struct audio_output *ao);
-
-bool
-ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &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,
- Error &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
deleted file mode 100644
index 30f2c6732..000000000
--- a/src/OutputPrint.cxx
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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
deleted file mode 100644
index 5d446d702..000000000
--- a/src/OutputPrint.hxx
+++ /dev/null
@@ -1,34 +0,0 @@
-
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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
deleted file mode 100644
index a3650413c..000000000
--- a/src/OutputState.cxx
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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 "OutputError.hxx"
-#include "Log.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) {
- FormatDebug(output_domain,
- "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
deleted file mode 100644
index 5ab765ba8..000000000
--- a/src/OutputState.hxx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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
deleted file mode 100644
index b96ba4efd..000000000
--- a/src/OutputThread.cxx
+++ /dev/null
@@ -1,688 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OutputThread.hxx"
-#include "OutputInternal.hxx"
-#include "OutputAPI.hxx"
-#include "OutputError.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 "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-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)
-{
- Error error;
- bool success;
-
- if (ao->really_enabled)
- return true;
-
- ao->mutex.unlock();
- success = ao_plugin_enable(ao, error);
- ao->mutex.lock();
- if (!success) {
- FormatError(error,
- "Failed to enable \"%s\" [%s]",
- ao->name, ao->plugin->name);
- 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,
- Error &error_r)
-{
- assert(format.IsValid());
-
- /* the replay_gain filter cannot fail here */
- if (ao->replay_gain_filter != nullptr)
- ao->replay_gain_filter->Open(format, error_r);
- if (ao->other_replay_gain_filter != nullptr)
- ao->other_replay_gain_filter->Open(format, error_r);
-
- const AudioFormat af = ao->filter->Open(format, error_r);
- if (!af.IsDefined()) {
- if (ao->replay_gain_filter != nullptr)
- ao->replay_gain_filter->Close();
- if (ao->other_replay_gain_filter != nullptr)
- ao->other_replay_gain_filter->Close();
- }
-
- return af;
-}
-
-static void
-ao_filter_close(struct audio_output *ao)
-{
- if (ao->replay_gain_filter != nullptr)
- ao->replay_gain_filter->Close();
- if (ao->other_replay_gain_filter != nullptr)
- ao->other_replay_gain_filter->Close();
-
- ao->filter->Close();
-}
-
-static void
-ao_open(struct audio_output *ao)
-{
- bool success;
- Error error;
- struct audio_format_string af_string;
-
- assert(!ao->open);
- assert(ao->pipe != nullptr);
- assert(ao->chunk == nullptr);
- assert(ao->in_audio_format.IsValid());
-
- if (ao->fail_timer != nullptr) {
- /* this can only happen when this
- output thread fails while
- audio_output_open() is run in the
- player thread */
- g_timer_destroy(ao->fail_timer);
- ao->fail_timer = nullptr;
- }
-
- /* 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()) {
- FormatError(error, "Failed to open filter for \"%s\" [%s]",
- ao->name, ao->plugin->name);
-
- 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) {
- FormatError(error, "Failed to open \"%s\" [%s]",
- ao->name, ao->plugin->name);
-
- ao_filter_close(ao);
- ao->fail_timer = g_timer_new();
- return;
- }
-
- convert_filter_set(ao->convert_filter, ao->out_audio_format);
-
- ao->open = true;
-
- FormatDebug(output_domain,
- "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)
- FormatDebug(output_domain, "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 = nullptr;
-
- ao->chunk = nullptr;
- 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();
-
- FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
- ao->plugin->name, ao->name);
-}
-
-static void
-ao_reopen_filter(struct audio_output *ao)
-{
- Error error;
-
- ao_filter_close(ao);
- const AudioFormat filter_audio_format =
- ao_filter_open(ao, ao->in_audio_format, error);
- if (!filter_audio_format.IsDefined()) {
- FormatError(error,
- "Failed to open filter for \"%s\" [%s]",
- ao->name, ao->plugin->name);
-
- /* 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 = nullptr;
-
- ao->chunk = nullptr;
- 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 MusicPipe *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 != nullptr);
- 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 != nullptr) {
- 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
- : nullptr);
- *replay_gain_serial_p = chunk->replay_gain_serial;
- }
-
- Error error;
- data = replay_gain_filter->FilterPCM(data, length,
- &length, error);
- if (data == nullptr) {
- FormatError(error, "\"%s\" [%s] failed to filter",
- ao->name, ao->plugin->name);
- return nullptr;
- }
- }
-
- *length_r = length;
- return data;
-}
-
-static const void *
-ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
- size_t *length_r)
-{
- size_t length;
- const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter,
- &ao->replay_gain_serial, &length);
- if (data == nullptr)
- return nullptr;
-
- if (length == 0) {
- /* empty chunk, nothing to do */
- *length_r = 0;
- return data;
- }
-
- /* cross-fade */
-
- if (chunk->other != nullptr) {
- 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 == nullptr)
- return nullptr;
-
- 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;
-
- float mix_ratio = chunk->mix_ratio;
- if (mix_ratio >= 0)
- /* reverse the mix ratio (because the
- arguments to pcm_mix() are reversed), but
- only if the mix ratio is non-negative; a
- negative mix ratio is a MixRamp special
- case */
- mix_ratio = 1.0 - mix_ratio;
-
- 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,
- mix_ratio)) {
- FormatError(output_domain,
- "Cannot cross-fade format %s",
- sample_format_to_string(ao->in_audio_format.format));
- return nullptr;
- }
-
- data = dest;
- length = other_length;
- }
-
- /* apply filter chain */
-
- Error error;
- data = ao->filter->FilterPCM(data, length, &length, error);
- if (data == nullptr) {
- FormatError(error, "\"%s\" [%s] failed to filter",
- ao->name, ao->plugin->name);
- return nullptr;
- }
-
- *length_r = length;
- return data;
-}
-
-static bool
-ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
-{
- assert(ao != nullptr);
- assert(ao->filter != nullptr);
-
- if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) {
- 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 == nullptr) {
- ao_close(ao, false);
-
- /* don't automatically reopen this device for 10
- seconds */
- ao->fail_timer = g_timer_new();
- return false;
- }
-
- Error error;
-
- 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 */
- FormatError(error, "\"%s\" [%s] failed to play",
- ao->name, ao->plugin->name);
-
- ao_close(ao, false);
-
- /* don't automatically reopen this device for
- 10 seconds */
- assert(ao->fail_timer == nullptr);
- 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 != nullptr
- /* continue the previous play() call */
- ? ao->chunk->next
- /* get the first chunk from the pipe */
- : ao->pipe->Peek();
-}
-
-/**
- * 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 != nullptr);
-
- chunk = ao_next_chunk(ao);
- if (chunk == nullptr)
- /* no chunk available */
- return false;
-
- ao->chunk_finished = false;
-
- assert(!ao->in_playback_loop);
- ao->in_playback_loop = true;
-
- while (chunk != nullptr && ao->command == AO_COMMAND_NONE) {
- assert(!ao->chunk_finished);
-
- ao->chunk = chunk;
-
- success = ao_play_chunk(ao, chunk);
- if (!success) {
- assert(ao->chunk == nullptr);
- break;
- }
-
- assert(ao->chunk == chunk);
- chunk = chunk->next;
- }
-
- assert(ao->in_playback_loop);
- ao->in_playback_loop = false;
-
- 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 void
-audio_output_task(void *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 != nullptr);
-
- 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 == nullptr);
- assert(ao->pipe->Peek() == nullptr);
-
- ao->mutex.unlock();
- ao_plugin_drain(ao);
- ao->mutex.lock();
- }
-
- ao_command_finished(ao);
- continue;
-
- case AO_COMMAND_CANCEL:
- ao->chunk = nullptr;
-
- if (ao->open) {
- ao->mutex.unlock();
- ao_plugin_cancel(ao);
- ao->mutex.lock();
- }
-
- ao_command_finished(ao);
- continue;
-
- case AO_COMMAND_KILL:
- ao->chunk = nullptr;
- ao_command_finished(ao);
- ao->mutex.unlock();
- return;
- }
-
- 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->woken_for_play = false;
- ao->cond.wait(ao->mutex);
- }
- }
-}
-
-void audio_output_thread_start(struct audio_output *ao)
-{
- assert(ao->command == AO_COMMAND_NONE);
-
- Error error;
- if (!ao->thread.Start(audio_output_task, ao, error))
- FatalError(error);
-}
diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx
deleted file mode 100644
index 1a7932162..000000000
--- a/src/OutputThread.hxx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 91033a1ec..000000000
--- a/src/Page.cxx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
deleted file mode 100644
index 27c6092cc..000000000
--- a/src/Page.hxx
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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.cxx b/src/Partition.cxx
index 55750cfad..de1170557 100644
--- a/src/Partition.cxx
+++ b/src/Partition.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,21 +19,29 @@
#include "config.h"
#include "Partition.hxx"
-#include "Song.hxx"
+#include "DetachedSong.hxx"
+#include "output/MultipleOutputs.hxx"
+#include "mixer/Volume.hxx"
+#include "Idle.hxx"
+#include "GlobalEvents.hxx"
+
+#ifdef ENABLE_DATABASE
void
-Partition::DatabaseModified()
+Partition::DatabaseModified(const Database &db)
{
- playlist.DatabaseModified();
+ playlist.DatabaseModified(db);
}
+#endif
+
void
Partition::TagModified()
{
- Song *song = pc.LockReadTaggedSong();
+ DetachedSong *song = pc.LockReadTaggedSong();
if (song != nullptr) {
playlist.TagModified(std::move(*song));
- song->Free();
+ delete song;
}
}
@@ -42,3 +50,24 @@ Partition::SyncWithPlayer()
{
playlist.SyncWithPlayer(pc);
}
+
+void
+Partition::OnPlayerSync()
+{
+ GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+}
+
+void
+Partition::OnPlayerTagModified()
+{
+ GlobalEvents::Emit(GlobalEvents::TAG);
+}
+
+void
+Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume)
+{
+ InvalidateHardwareVolume();
+
+ /* notify clients */
+ idle_add(IDLE_MIXER);
+}
diff --git a/src/Partition.hxx b/src/Partition.hxx
index 512ba3bca..d89c4b41f 100644
--- a/src/Partition.hxx
+++ b/src/Partition.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,20 +20,29 @@
#ifndef MPD_PARTITION_HXX
#define MPD_PARTITION_HXX
-#include "Playlist.hxx"
+#include "queue/Playlist.hxx"
+#include "output/MultipleOutputs.hxx"
+#include "mixer/Listener.hxx"
#include "PlayerControl.hxx"
+#include "PlayerListener.hxx"
+#include "Chrono.hxx"
+#include "Compiler.h"
struct Instance;
+class MultipleOutputs;
+class SongLoader;
/**
* A partition of the Music Player Daemon. It is a separate unit with
* a playlist, a player, outputs etc.
*/
-struct Partition {
+struct Partition final : private PlayerListener, private MixerListener {
Instance &instance;
struct playlist playlist;
+ MultipleOutputs outputs;
+
PlayerControl pc;
Partition(Instance &_instance,
@@ -41,21 +50,17 @@ struct Partition {
unsigned buffer_chunks,
unsigned buffered_before_play)
:instance(_instance), playlist(max_length),
- pc(buffer_chunks, buffered_before_play) {
- }
+ outputs(*this),
+ pc(*this, outputs, buffer_chunks, buffered_before_play) {}
void ClearQueue() {
playlist.Clear(pc);
}
- PlaylistResult AppendFile(const char *path_utf8,
- unsigned *added_id=nullptr) {
- return playlist.AppendFile(pc, path_utf8, added_id);
- }
-
- PlaylistResult AppendURI(const char *uri_utf8,
- unsigned *added_id=nullptr) {
- return playlist.AppendURI(pc, uri_utf8, added_id);
+ unsigned AppendURI(const SongLoader &loader,
+ const char *uri_utf8,
+ Error &error) {
+ return playlist.AppendURI(pc, loader, uri_utf8, error);
}
PlaylistResult DeletePosition(unsigned position) {
@@ -76,10 +81,14 @@ struct Partition {
return playlist.DeleteRange(pc, start, end);
}
- void DeleteSong(const Song &song) {
- playlist.DeleteSong(pc, song);
+#ifdef ENABLE_DATABASE
+
+ void DeleteSong(const char *uri) {
+ playlist.DeleteSong(pc, uri);
}
+#endif
+
void Shuffle(unsigned start, unsigned end) {
playlist.Shuffle(pc, start, end);
}
@@ -134,15 +143,15 @@ struct Partition {
}
PlaylistResult SeekSongPosition(unsigned song_position,
- float seek_time) {
+ SongTime seek_time) {
return playlist.SeekSongPosition(pc, song_position, seek_time);
}
- PlaylistResult SeekSongId(unsigned song_id, float seek_time) {
+ PlaylistResult SeekSongId(unsigned song_id, SongTime seek_time) {
return playlist.SeekSongId(pc, song_id, seek_time);
}
- PlaylistResult SeekCurrent(float seek_time, bool relative) {
+ PlaylistResult SeekCurrent(SignedSongTime seek_time, bool relative) {
return playlist.SeekCurrent(pc, seek_time, relative);
}
@@ -166,11 +175,13 @@ struct Partition {
playlist.SetConsume(new_value);
}
+#ifdef ENABLE_DATABASE
/**
* The database has been modified. Propagate the change to
* all subsystems.
*/
- void DatabaseModified();
+ void DatabaseModified(const Database &db);
+#endif
/**
* A tag in the play queue has been modified by the player
@@ -182,6 +193,14 @@ struct Partition {
* Synchronize the player with the play queue.
*/
void SyncWithPlayer();
+
+private:
+ /* virtual methods from class PlayerListener */
+ virtual void OnPlayerSync() override;
+ virtual void OnPlayerTagModified() override;
+
+ /* virtual methods from class MixerListener */
+ virtual void OnMixerVolumeChanged(Mixer &mixer, int volume) override;
};
#endif
diff --git a/src/Permission.cxx b/src/Permission.cxx
index a35c80e94..d6c267ab7 100644
--- a/src/Permission.cxx
+++ b/src/Permission.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,9 +19,9 @@
#include "config.h"
#include "Permission.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
#include "system/FatalError.hxx"
#include <algorithm>
@@ -92,7 +92,7 @@ void initPermissions(void)
permission_default = PERMISSION_READ | PERMISSION_ADD |
PERMISSION_CONTROL | PERMISSION_ADMIN;
- param = config_get_next_param(CONF_PASSWORD, NULL);
+ param = config_get_param(CONF_PASSWORD);
if (param) {
permission_default = 0;
@@ -115,7 +115,7 @@ void initPermissions(void)
permission_passwords.insert(std::make_pair(std::move(password),
permission));
- } while ((param = config_get_next_param(CONF_PASSWORD, param)));
+ } while ((param = param->next) != nullptr);
}
param = config_get_param(CONF_DEFAULT_PERMS);
diff --git a/src/Permission.hxx b/src/Permission.hxx
index 228e9ee20..60761c696 100644
--- a/src/Permission.hxx
+++ b/src/Permission.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx
index f180874b0..4f1c3d2ac 100644
--- a/src/PlayerControl.cxx
+++ b/src/PlayerControl.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,16 +20,18 @@
#include "config.h"
#include "PlayerControl.hxx"
#include "Idle.hxx"
-#include "Song.hxx"
-#include "DecoderControl.hxx"
+#include "DetachedSong.hxx"
-#include <cmath>
+#include <algorithm>
#include <assert.h>
-PlayerControl::PlayerControl(unsigned _buffer_chunks,
+PlayerControl::PlayerControl(PlayerListener &_listener,
+ MultipleOutputs &_outputs,
+ unsigned _buffer_chunks,
unsigned _buffered_before_play)
- :buffer_chunks(_buffer_chunks),
+ :listener(_listener), outputs(_outputs),
+ buffer_chunks(_buffer_chunks),
buffered_before_play(_buffered_before_play),
command(PlayerCommand::NONE),
state(PlayerState::STOP),
@@ -43,15 +45,12 @@ PlayerControl::PlayerControl(unsigned _buffer_chunks,
PlayerControl::~PlayerControl()
{
- if (next_song != nullptr)
- next_song->Free();
-
- if (tagged_song != nullptr)
- tagged_song->Free();
+ delete next_song;
+ delete tagged_song;
}
void
-PlayerControl::Play(Song *song)
+PlayerControl::Play(DetachedSong *song)
{
assert(song != nullptr);
@@ -196,26 +195,23 @@ PlayerControl::ClearError()
}
void
-PlayerControl::LockSetTaggedSong(const Song &song)
+PlayerControl::LockSetTaggedSong(const DetachedSong &song)
{
Lock();
- if (tagged_song != nullptr)
- tagged_song->Free();
- tagged_song = song.DupDetached();
+ delete tagged_song;
+ tagged_song = new DetachedSong(song);
Unlock();
}
void
PlayerControl::ClearTaggedSong()
{
- if (tagged_song != nullptr) {
- tagged_song->Free();
- tagged_song = nullptr;
- }
+ delete tagged_song;
+ tagged_song = nullptr;
}
void
-PlayerControl::EnqueueSong(Song *song)
+PlayerControl::EnqueueSong(DetachedSong *song)
{
assert(song != nullptr);
@@ -225,17 +221,15 @@ PlayerControl::EnqueueSong(Song *song)
}
bool
-PlayerControl::Seek(Song *song, float seek_time)
+PlayerControl::Seek(DetachedSong *song, SongTime t)
{
assert(song != nullptr);
Lock();
- if (next_song != nullptr)
- next_song->Free();
-
+ delete next_song;
next_song = song;
- seek_where = seek_time;
+ seek_time = t;
SynchronousCommand(PlayerCommand::SEEK);
Unlock();
diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx
index 61bb408d2..4d06a1827 100644
--- a/src/PlayerControl.hxx
+++ b/src/PlayerControl.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_PLAYER_H
-#define MPD_PLAYER_H
+#ifndef MPD_PLAYER_CONTROL_HXX
+#define MPD_PLAYER_CONTROL_HXX
#include "AudioFormat.hxx"
#include "thread/Mutex.hxx"
@@ -26,10 +26,13 @@
#include "thread/Thread.hxx"
#include "util/Error.hxx"
#include "CrossFade.hxx"
+#include "Chrono.hxx"
#include <stdint.h>
-struct Song;
+class PlayerListener;
+class MultipleOutputs;
+class DetachedSong;
enum class PlayerState : uint8_t {
STOP,
@@ -46,7 +49,7 @@ enum class PlayerCommand : uint8_t {
CLOSE_AUDIO,
/**
- * At least one audio_output.enabled flag has been modified;
+ * At least one AudioOutput.enabled flag has been modified;
* commit those changes to the output threads.
*/
UPDATE_AUDIO,
@@ -86,14 +89,18 @@ struct player_status {
PlayerState state;
uint16_t bit_rate;
AudioFormat audio_format;
- float total_time;
- float elapsed_time;
+ SignedSongTime total_time;
+ SongTime elapsed_time;
};
struct PlayerControl {
- unsigned buffer_chunks;
+ PlayerListener &listener;
- unsigned int buffered_before_play;
+ MultipleOutputs &outputs;
+
+ const unsigned buffer_chunks;
+
+ const unsigned buffered_before_play;
/**
* The handle of the player thread.
@@ -131,21 +138,21 @@ struct PlayerControl {
Error error;
/**
- * A copy of the current #Song after its tags have been
- * updated by the decoder (for example, a radio stream that
- * has sent a new tag after switching to the next song). This
- * shall be used by the GlobalEvents::TAG handler to update
- * the current #Song in the queue.
+ * A copy of the current #DetachedSong after its tags have
+ * been updated by the decoder (for example, a radio stream
+ * that has sent a new tag after switching to the next song).
+ * This shall be used by PlayerListener::OnPlayerTagModified()
+ * to update the current #DetachedSong in the queue.
*
* Protected by #mutex. Set by the PlayerThread and consumed
* by the main thread.
*/
- Song *tagged_song;
+ DetachedSong *tagged_song;
uint16_t bit_rate;
AudioFormat audio_format;
- float total_time;
- float elapsed_time;
+ SignedSongTime total_time;
+ SongTime elapsed_time;
/**
* The next queued song.
@@ -153,9 +160,9 @@ struct PlayerControl {
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
- Song *next_song;
+ DetachedSong *next_song;
- double seek_where;
+ SongTime seek_time;
CrossFadeSettings cross_fade;
@@ -170,7 +177,9 @@ struct PlayerControl {
*/
bool border_pause;
- PlayerControl(unsigned buffer_chunks,
+ PlayerControl(PlayerListener &_listener,
+ MultipleOutputs &_outputs,
+ unsigned buffer_chunks,
unsigned buffered_before_play);
~PlayerControl();
@@ -299,7 +308,7 @@ public:
* @param song the song to be queued; the given instance will
* be owned and freed by the player
*/
- void Play(Song *song);
+ void Play(DetachedSong *song);
/**
* see PlayerCommand::CANCEL
@@ -371,9 +380,9 @@ public:
/**
* Set the #tagged_song attribute to a newly allocated copy of
- * the given #Song. Locks and unlocks the object.
+ * the given #DetachedSong. Locks and unlocks the object.
*/
- void LockSetTaggedSong(const Song &song);
+ void LockSetTaggedSong(const DetachedSong &song);
void ClearTaggedSong();
@@ -382,8 +391,8 @@ public:
*
* Caller must lock the object.
*/
- Song *ReadTaggedSong() {
- Song *result = tagged_song;
+ DetachedSong *ReadTaggedSong() {
+ DetachedSong *result = tagged_song;
tagged_song = nullptr;
return result;
}
@@ -391,9 +400,9 @@ public:
/**
* Like ReadTaggedSong(), but locks and unlocks the object.
*/
- Song *LockReadTaggedSong() {
+ DetachedSong *LockReadTaggedSong() {
Lock();
- Song *result = ReadTaggedSong();
+ DetachedSong *result = ReadTaggedSong();
Unlock();
return result;
}
@@ -403,7 +412,7 @@ public:
void UpdateAudio();
private:
- void EnqueueSongLocked(Song *song) {
+ void EnqueueSongLocked(DetachedSong *song) {
assert(song != nullptr);
assert(next_song == nullptr);
@@ -416,7 +425,7 @@ public:
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
- void EnqueueSong(Song *song);
+ void EnqueueSong(DetachedSong *song);
/**
* Makes the player thread seek the specified song to a position.
@@ -426,7 +435,7 @@ public:
* @return true on success, false on failure (e.g. if MPD isn't
* playing currently)
*/
- bool Seek(Song *song, float seek_time);
+ bool Seek(DetachedSong *song, SongTime t);
void SetCrossFade(float cross_fade_seconds);
diff --git a/src/PlayerListener.hxx b/src/PlayerListener.hxx
new file mode 100644
index 000000000..06f00a4f5
--- /dev/null
+++ b/src/PlayerListener.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYER_LISTENER_HXX
+#define MPD_PLAYER_LISTENER_HXX
+
+class PlayerListener {
+public:
+ /**
+ * Must call playlist_sync().
+ */
+ virtual void OnPlayerSync() = 0;
+
+ /**
+ * The current song's tag has changed.
+ */
+ virtual void OnPlayerTagModified() = 0;
+};
+
+#endif
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
index 356559e37..c5308e612 100644
--- a/src/PlayerThread.cxx
+++ b/src/PlayerThread.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,21 +19,21 @@
#include "config.h"
#include "PlayerThread.hxx"
-#include "DecoderThread.hxx"
-#include "DecoderControl.hxx"
+#include "PlayerListener.hxx"
+#include "decoder/DecoderThread.hxx"
+#include "decoder/DecoderControl.hxx"
#include "MusicPipe.hxx"
#include "MusicBuffer.hxx"
#include "MusicChunk.hxx"
-#include "Song.hxx"
-#include "Main.hxx"
+#include "DetachedSong.hxx"
#include "system/FatalError.hxx"
#include "CrossFade.hxx"
#include "PlayerControl.hxx"
-#include "OutputAll.hxx"
+#include "output/MultipleOutputs.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
-#include "GlobalEvents.hxx"
#include "util/Domain.hxx"
+#include "thread/Name.hxx"
#include "Log.hxx"
#include <string.h>
@@ -93,7 +93,7 @@ class Player {
/**
* the song currently being played
*/
- Song *song;
+ DetachedSong *song;
/**
* is cross fading enabled?
@@ -125,11 +125,11 @@ class Player {
/**
* 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
+ * MultipleOutputs::GetElapsedTime() didn't return a usable
* value; the output thread can estimate the elapsed time more
* precisely.
*/
- float elapsed_time;
+ SongTime elapsed_time;
public:
Player(PlayerControl &_pc, DecoderControl &_dc,
@@ -146,7 +146,7 @@ public:
cross_fading(false),
cross_fade_chunks(0),
cross_fade_tag(nullptr),
- elapsed_time(0.0) {}
+ elapsed_time(SongTime::zero()) {}
private:
void ClearAndDeletePipe() {
@@ -228,8 +228,8 @@ private:
bool WaitForDecoder();
/**
- * Wrapper for audio_output_all_open(). Upon failure, it pauses the
- * player.
+ * Wrapper for MultipleOutputs::Open(). Upon failure, it
+ * pauses the player.
*
* @return true on success
*/
@@ -291,12 +291,12 @@ Player::StartDecoder(MusicPipe &_pipe)
assert(queued || pc.command == PlayerCommand::SEEK);
assert(pc.next_song != nullptr);
- unsigned start_ms = pc.next_song->start_ms;
+ SongTime start_time = pc.next_song->GetStartTime();
if (pc.command == PlayerCommand::SEEK)
- start_ms += (unsigned)(pc.seek_where * 1000);
+ start_time += pc.seek_time;
- dc.Start(pc.next_song->DupDetached(),
- start_ms, pc.next_song->end_ms,
+ dc.Start(new DetachedSong(*pc.next_song),
+ start_time, pc.next_song->GetEndTime(),
buffer, _pipe);
}
@@ -330,7 +330,7 @@ Player::WaitForDecoder()
if (error.IsDefined()) {
pc.SetError(PlayerError::DECODER, std::move(error));
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
pc.Unlock();
@@ -340,11 +340,9 @@ Player::WaitForDecoder()
pc.ClearTaggedSong();
- if (song != nullptr)
- song->Free();
-
+ delete song;
song = pc.next_song;
- elapsed_time = 0.0;
+ elapsed_time = SongTime::zero();
/* set the "starting" flag, which will be cleared by
player_check_decoder_startup() */
@@ -361,7 +359,7 @@ Player::WaitForDecoder()
pc.Unlock();
/* call syncPlaylistWithQueue() in the main thread */
- GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+ pc.listener.OnPlayerSync();
return true;
}
@@ -370,20 +368,21 @@ Player::WaitForDecoder()
* 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)
+static SignedSongTime
+real_song_duration(const DetachedSong &song, SignedSongTime decoder_duration)
{
- assert(song != nullptr);
-
- if (decoder_duration <= 0.0)
+ if (decoder_duration.IsNegative())
/* the decoder plugin didn't provide information; fall
back to Song::GetDuration() */
- return song->GetDuration();
+ return song.GetDuration();
+
+ const SongTime start_time = song.GetStartTime();
+ const SongTime end_time = song.GetEndTime();
- if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration)
- return (song->end_ms - song->start_ms) / 1000.0;
+ if (end_time.IsPositive() && end_time < SongTime(decoder_duration))
+ return SignedSongTime(end_time - start_time);
- return decoder_duration - song->start_ms / 1000.0;
+ return SignedSongTime(SongTime(decoder_duration) - start_time);
}
bool
@@ -394,7 +393,7 @@ Player::OpenOutput()
pc.state == PlayerState::PAUSE);
Error error;
- if (audio_output_all_open(play_audio_format, buffer, error)) {
+ if (pc.outputs.Open(play_audio_format, buffer, error)) {
output_open = true;
paused = false;
@@ -445,13 +444,13 @@ Player::CheckDecoderStartup()
pc.Unlock();
if (output_open &&
- !audio_output_all_wait(pc, 1))
+ !pc.outputs.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.total_time = real_song_duration(*dc.song, dc.total_time);
pc.audio_format = dc.in_audio_format;
pc.Unlock();
@@ -461,10 +460,10 @@ Player::CheckDecoderStartup()
decoder_starting = false;
if (!paused && !OpenOutput()) {
- const auto uri = dc.song->GetURI();
FormatError(player_domain,
"problems opening audio device "
- "while playing \"%s\"", uri.c_str());
+ "while playing \"%s\"",
+ dc.song->GetURI());
return true;
}
@@ -485,7 +484,7 @@ Player::SendSilence()
assert(output_open);
assert(play_audio_format.IsDefined());
- struct music_chunk *chunk = buffer.Allocate();
+ MusicChunk *chunk = buffer.Allocate();
if (chunk == nullptr) {
LogError(player_domain, "Failed to allocate silence buffer");
return false;
@@ -500,12 +499,12 @@ Player::SendSilence()
partial frames */
unsigned num_frames = sizeof(chunk->data) / frame_size;
- chunk->times = -1.0; /* undefined time stamp */
+ chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
chunk->length = num_frames * frame_size;
memset(chunk->data, 0, chunk->length);
Error error;
- if (!audio_output_all_play(chunk, error)) {
+ if (!pc.outputs.Play(chunk, error)) {
LogError(error);
buffer.Return(chunk);
return false;
@@ -519,7 +518,7 @@ Player::SeekDecoder()
{
assert(pc.next_song != nullptr);
- const unsigned start_ms = pc.next_song->start_ms;
+ const SongTime start_time = pc.next_song->GetStartTime();
if (!dc.LockIsCurrentSong(*pc.next_song)) {
/* the decoder is already decoding the "next" song -
@@ -545,7 +544,7 @@ Player::SeekDecoder()
ClearAndReplacePipe(dc.pipe);
}
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
queued = false;
}
@@ -562,13 +561,14 @@ Player::SeekDecoder()
/* 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;
+ SongTime where = pc.seek_time;
+ if (!pc.total_time.IsNegative()) {
+ const SongTime total_time(pc.total_time);
+ if (where > total_time)
+ where = total_time;
+ }
- if (!dc.Seek(where + start_ms / 1000.0)) {
+ if (!dc.Seek(where + start_time)) {
/* decoder failure */
player_command_finished(pc);
return false;
@@ -583,7 +583,7 @@ Player::SeekDecoder()
/* re-fill the buffer after seeking */
buffering = true;
- audio_output_all_cancel();
+ pc.outputs.Cancel();
return true;
}
@@ -600,7 +600,7 @@ Player::ProcessCommand()
case PlayerCommand::UPDATE_AUDIO:
pc.Unlock();
- audio_output_all_enable_disable();
+ pc.outputs.EnableDisable();
pc.Lock();
pc.CommandFinished();
break;
@@ -619,7 +619,7 @@ Player::ProcessCommand()
paused = !paused;
if (paused) {
- audio_output_all_pause();
+ pc.outputs.Pause();
pc.Lock();
pc.state = PlayerState::PAUSE;
@@ -661,7 +661,7 @@ Player::ProcessCommand()
pc.Lock();
}
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
queued = false;
pc.CommandFinished();
@@ -670,13 +670,13 @@ Player::ProcessCommand()
case PlayerCommand::REFRESH:
if (output_open && !paused) {
pc.Unlock();
- audio_output_all_check();
+ pc.outputs.Check();
pc.Lock();
}
- pc.elapsed_time = audio_output_all_get_elapsed_time();
- if (pc.elapsed_time < 0.0)
- pc.elapsed_time = elapsed_time;
+ pc.elapsed_time = !pc.outputs.GetElapsedTime().IsNegative()
+ ? SongTime(pc.outputs.GetElapsedTime())
+ : elapsed_time;
pc.CommandFinished();
break;
@@ -684,23 +684,20 @@ Player::ProcessCommand()
}
static void
-update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag)
+update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag)
{
- if (song->IsFile())
+ if (song.IsFile())
/* don't update tags of local files, only remote
streams may change tags dynamically */
return;
- Tag *old_tag = song->tag;
- song->tag = new Tag(new_tag);
-
- delete old_tag;
+ song.SetTag(new_tag);
- pc.LockSetTaggedSong(*song);
+ pc.LockSetTaggedSong(song);
/* the main thread will update the playlist version when he
receives this event */
- GlobalEvents::Emit(GlobalEvents::TAG);
+ pc.listener.OnPlayerTagModified();
/* notify all clients that the tag of the current song has
changed */
@@ -708,7 +705,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag)
}
/**
- * Plays a #music_chunk object (after applying software volume). If
+ * Plays a #MusicChunk 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.
*
@@ -716,7 +713,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag)
*/
static bool
play_chunk(PlayerControl &pc,
- Song *song, struct music_chunk *chunk,
+ DetachedSong &song, MusicChunk *chunk,
MusicBuffer &buffer,
const AudioFormat format,
Error &error)
@@ -737,7 +734,7 @@ play_chunk(PlayerControl &pc,
/* send the chunk to the audio outputs */
- if (!audio_output_all_play(chunk, error))
+ if (!pc.outputs.Play(chunk, error))
return false;
pc.total_play_time += (double)chunk->length /
@@ -748,17 +745,17 @@ play_chunk(PlayerControl &pc,
inline bool
Player::PlayNextChunk()
{
- if (!audio_output_all_wait(pc, 64))
+ if (!pc.outputs.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 = nullptr;
+ MusicChunk *chunk = nullptr;
if (xfade_state == CrossFadeState::ENABLED && IsDecoderAtNextSong() &&
(cross_fade_position = pipe->GetSize()) <= cross_fade_chunks) {
/* perform cross fade */
- music_chunk *other_chunk = dc.pipe->Shift();
+ MusicChunk *other_chunk = dc.pipe->Shift();
if (!cross_fading) {
/* beginning of the cross fade - adjust
@@ -790,7 +787,7 @@ Player::PlayNextChunk()
}
if (other_chunk->IsEmpty()) {
- /* the "other" chunk was a music_chunk
+ /* the "other" chunk was a MusicChunk
which had only a tag, but no music
data - we cannot cross-fade that;
but since this happens only at the
@@ -839,7 +836,7 @@ Player::PlayNextChunk()
/* play the current chunk */
Error error;
- if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) {
+ if (!play_chunk(pc, *song, chunk, buffer, play_audio_format, error)) {
LogError(error);
buffer.Return(chunk);
@@ -883,14 +880,11 @@ Player::SongBorder()
{
xfade_state = CrossFadeState::UNKNOWN;
- {
- const auto uri = song->GetURI();
- FormatDefault(player_domain, "played \"%s\"", uri.c_str());
- }
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
ReplacePipe(dc.pipe);
- audio_output_all_song_border();
+ pc.outputs.SongBorder();
if (!WaitForDecoder())
return false;
@@ -930,7 +924,7 @@ Player::Run()
pc.state = PlayerState::PLAY;
if (pc.command == PlayerCommand::SEEK)
- elapsed_time = pc.seek_where;
+ elapsed_time = pc.seek_time;
pc.CommandFinished();
@@ -940,7 +934,7 @@ Player::Run()
pc.command == PlayerCommand::EXIT ||
pc.command == PlayerCommand::CLOSE_AUDIO) {
pc.Unlock();
- audio_output_all_cancel();
+ pc.outputs.Cancel();
break;
}
@@ -956,7 +950,7 @@ Player::Run()
/* not enough decoded buffer space yet */
if (!paused && output_open &&
- audio_output_all_check() < 4 &&
+ pc.outputs.Check() < 4 &&
!SendSilence())
break;
@@ -1036,7 +1030,7 @@ Player::Run()
to the audio output */
PlayNextChunk();
- } else if (audio_output_all_check() > 0) {
+ } else if (pc.outputs.Check() > 0) {
/* not enough data from decoder, but the
output thread is still busy, so it's
okay */
@@ -1061,7 +1055,7 @@ Player::Run()
if (pipe->IsEmpty()) {
/* wait for the hardware to finish
playback */
- audio_output_all_drain();
+ pc.outputs.Drain();
break;
}
} else if (output_open) {
@@ -1082,9 +1076,8 @@ Player::Run()
delete cross_fade_tag;
if (song != nullptr) {
- const auto uri = song->GetURI();
- FormatDefault(player_domain, "played \"%s\"", uri.c_str());
- song->Free();
+ FormatDefault(player_domain, "played \"%s\"", song->GetURI());
+ delete song;
}
pc.Lock();
@@ -1093,7 +1086,7 @@ Player::Run()
if (queued) {
assert(pc.next_song != nullptr);
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
}
@@ -1115,6 +1108,8 @@ player_task(void *arg)
{
PlayerControl &pc = *(PlayerControl *)arg;
+ SetThreadName("player");
+
DecoderControl dc(pc.mutex, pc.cond);
decoder_thread_start(dc);
@@ -1130,22 +1125,20 @@ player_task(void *arg)
pc.Unlock();
do_play(pc, dc, buffer);
- GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+ pc.listener.OnPlayerSync();
pc.Lock();
break;
case PlayerCommand::STOP:
pc.Unlock();
- audio_output_all_cancel();
+ pc.outputs.Cancel();
pc.Lock();
/* fall through */
case PlayerCommand::PAUSE:
- if (pc.next_song != nullptr) {
- pc.next_song->Free();
- pc.next_song = nullptr;
- }
+ delete pc.next_song;
+ pc.next_song = nullptr;
pc.CommandFinished();
break;
@@ -1153,7 +1146,7 @@ player_task(void *arg)
case PlayerCommand::CLOSE_AUDIO:
pc.Unlock();
- audio_output_all_release();
+ pc.outputs.Release();
pc.Lock();
pc.CommandFinished();
@@ -1164,7 +1157,7 @@ player_task(void *arg)
case PlayerCommand::UPDATE_AUDIO:
pc.Unlock();
- audio_output_all_enable_disable();
+ pc.outputs.EnableDisable();
pc.Lock();
pc.CommandFinished();
break;
@@ -1174,16 +1167,14 @@ player_task(void *arg)
dc.Quit();
- audio_output_all_close();
+ pc.outputs.Close();
player_command_finished(pc);
return;
case PlayerCommand::CANCEL:
- if (pc.next_song != nullptr) {
- pc.next_song->Free();
- pc.next_song = nullptr;
- }
+ delete pc.next_song;
+ pc.next_song = nullptr;
pc.CommandFinished();
break;
@@ -1201,7 +1192,7 @@ player_task(void *arg)
}
void
-player_create(PlayerControl &pc)
+StartPlayerThread(PlayerControl &pc)
{
assert(!pc.thread.IsDefined());
diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx
index efdcf05ca..537e38399 100644
--- a/src/PlayerThread.hxx
+++ b/src/PlayerThread.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
*
* 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
+ * #MusicChunk objects from the decoder, optionally mixes them
* (cross-fading), applies software volume, and sends them to the
* audio outputs via audio_output_all_play().
*
@@ -31,7 +31,7 @@
*
* 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 #MusicPipe objects.
+ * #MusicChunk instances around in #MusicPipe objects.
*/
#ifndef MPD_PLAYER_THREAD_HXX
@@ -40,6 +40,6 @@
struct PlayerControl;
void
-player_create(PlayerControl &pc);
+StartPlayerThread(PlayerControl &pc);
#endif
diff --git a/src/Playlist.cxx b/src/Playlist.cxx
deleted file mode 100644
index 526f35298..000000000
--- a/src/Playlist.cxx
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Playlist.hxx"
-#include "PlaylistError.hxx"
-#include "PlayerControl.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "Idle.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-void
-playlist::TagModified(Song &&song)
-{
- if (!playing || song.tag == nullptr)
- return;
-
- assert(current >= 0);
-
- Song &current_song = queue.GetOrder(current);
- if (SongEquals(song, current_song))
- current_song.ReplaceTag(std::move(*song.tag));
-
- queue.ModifyAtOrder(current);
- queue.IncrementVersion();
- idle_add(IDLE_PLAYLIST);
-}
-
-/**
- * Queue a song, addressed by its order number.
- */
-static void
-playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
- unsigned order)
-{
- assert(playlist.queue.IsValidOrder(order));
-
- playlist.queued = order;
-
- Song *song = playlist.queue.GetOrder(order).DupDetached();
-
- {
- const auto uri = song->GetURI();
- FormatDebug(playlist_domain, "queue song %i:\"%s\"",
- playlist.queued, uri.c_str());
- }
-
- pc.EnqueueSong(song);
-}
-
-/**
- * Called if the player thread has started playing the "queued" song.
- */
-static void
-playlist_song_started(playlist &playlist, PlayerControl &pc)
-{
- assert(pc.next_song == nullptr);
- 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(PlayerControl &pc, const Song *prev)
-{
- if (!playing)
- return;
-
- if (prev == nullptr && bulk_edit)
- /* postponed until CommitBulk() to avoid always
- queueing the first song that is being added (in
- random mode) */
- return;
-
- assert(!queue.IsEmpty());
- assert((queued < 0) == (prev == nullptr));
-
- 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 != nullptr && 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(PlayerControl &pc, int order)
-{
- playing = true;
- queued = -1;
-
- Song *song = queue.GetOrder(order).DupDetached();
-
- {
- const auto uri = song->GetURI();
- FormatDebug(playlist_domain, "play %i:\"%s\"",
- order, uri.c_str());
- }
-
- pc.Play(song);
- current = order;
-}
-
-static void
-playlist_resume_playback(playlist &playlist, PlayerControl &pc);
-
-void
-playlist::SyncWithPlayer(PlayerControl &pc)
-{
- if (!playing)
- /* this event has reached us out of sync: we aren't
- playing anymore; ignore the event */
- return;
-
- pc.Lock();
- const PlayerState pc_state = pc.GetState();
- const Song *pc_next_song = pc.next_song;
- pc.Unlock();
-
- if (pc_state == PlayerState::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(playlist &playlist, PlayerControl &pc)
-{
- assert(playlist.playing);
- assert(pc.GetState() == PlayerState::STOP);
-
- const auto error = pc.GetErrorType();
- if (error == PlayerError::NONE)
- playlist.error_count = 0;
- else
- ++playlist.error_count;
-
- if ((playlist.stop_on_error && error != PlayerError::NONE) ||
- error == PlayerError::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(PlayerControl &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(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(PlayerControl &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(PlayerControl &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 = playing
- ? GetCurrentPosition()
- : -1;
-
- 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
deleted file mode 100644
index 582b3648a..000000000
--- a/src/Playlist.hxx
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_HXX
-#define MPD_PLAYLIST_HXX
-
-#include "Queue.hxx"
-#include "PlaylistError.hxx"
-
-struct PlayerControl;
-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;
-
- /**
- * If true, then a bulk edit has been initiated by
- * BeginBulk(), and UpdateQueuedSong() and OnModified() will
- * be postponed until CommitBulk()
- */
- bool bulk_edit;
-
- /**
- * Has the queue been modified during bulk edit mode?
- */
- bool bulk_modified;
-
- /**
- * 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),
- bulk_edit(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(PlayerControl &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(PlayerControl &pc, const Song *prev);
-
-public:
- void BeginBulk();
- void CommitBulk(PlayerControl &pc);
-
- void Clear(PlayerControl &pc);
-
- /**
- * A tag in the play queue has been modified by the player
- * thread. Apply the given song's tag to the current song if
- * the song matches.
- */
- void TagModified(Song &&song);
-
- /**
- * The database has been modified. Pull all updates.
- */
- void DatabaseModified();
-
- PlaylistResult AppendSong(PlayerControl &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.
- */
- PlaylistResult AppendFile(PlayerControl &pc,
- const char *path_utf8,
- unsigned *added_id=nullptr);
-
- PlaylistResult AppendURI(PlayerControl &pc,
- const char *uri_utf8,
- unsigned *added_id=nullptr);
-
-protected:
- void DeleteInternal(PlayerControl &pc,
- unsigned song, const Song **queued_p);
-
-public:
- PlaylistResult DeletePosition(PlayerControl &pc,
- unsigned position);
-
- PlaylistResult DeleteOrder(PlayerControl &pc,
- unsigned order) {
- return DeletePosition(pc, queue.OrderToPosition(order));
- }
-
- PlaylistResult DeleteId(PlayerControl &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
- */
- PlaylistResult DeleteRange(PlayerControl &pc,
- unsigned start, unsigned end);
-
- void DeleteSong(PlayerControl &pc, const Song &song);
-
- void Shuffle(PlayerControl &pc, unsigned start, unsigned end);
-
- PlaylistResult MoveRange(PlayerControl &pc,
- unsigned start, unsigned end, int to);
-
- PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to);
-
- PlaylistResult SwapPositions(PlayerControl &pc,
- unsigned song1, unsigned song2);
-
- PlaylistResult SwapIds(PlayerControl &pc,
- unsigned id1, unsigned id2);
-
- PlaylistResult SetPriorityRange(PlayerControl &pc,
- unsigned start_position,
- unsigned end_position,
- uint8_t priority);
-
- PlaylistResult SetPriorityId(PlayerControl &pc,
- unsigned song_id, uint8_t priority);
-
- void Stop(PlayerControl &pc);
-
- PlaylistResult PlayPosition(PlayerControl &pc, int position);
-
- void PlayOrder(PlayerControl &pc, int order);
-
- PlaylistResult PlayId(PlayerControl &pc, int id);
-
- void PlayNext(PlayerControl &pc);
-
- void PlayPrevious(PlayerControl &pc);
-
- PlaylistResult SeekSongOrder(PlayerControl &pc,
- unsigned song_order,
- float seek_time);
-
- PlaylistResult SeekSongPosition(PlayerControl &pc,
- unsigned song_position,
- float seek_time);
-
- PlaylistResult SeekSongId(PlayerControl &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
- */
- PlaylistResult SeekCurrent(PlayerControl &pc,
- float seek_time, bool relative);
-
- bool GetRepeat() const {
- return queue.repeat;
- }
-
- void SetRepeat(PlayerControl &pc, bool new_value);
-
- bool GetRandom() const {
- return queue.random;
- }
-
- void SetRandom(PlayerControl &pc, bool new_value);
-
- bool GetSingle() const {
- return queue.single;
- }
-
- void SetSingle(PlayerControl &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
deleted file mode 100644
index 52304700f..000000000
--- a/src/PlaylistAny.cxx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlaylistAny.hxx"
-#include "PlaylistMapper.hxx"
-#include "PlaylistRegistry.hxx"
-#include "PlaylistError.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
-#include "InputStream.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-static SongEnumerator *
-playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **is_r)
-{
- assert(uri_has_scheme(uri));
-
- SongEnumerator *playlist = playlist_list_open_uri(uri, mutex, cond);
- if (playlist != nullptr) {
- *is_r = nullptr;
- return playlist;
- }
-
- Error error;
- InputStream *is = InputStream::Open(uri, mutex, cond, error);
- if (is == nullptr) {
- if (error.IsDefined())
- FormatError(error, "Failed to open %s", uri);
-
- return nullptr;
- }
-
- playlist = playlist_list_open_stream(*is, uri);
- if (playlist == nullptr) {
- is->Close();
- return nullptr;
- }
-
- *is_r = is;
- return playlist;
-}
-
-SongEnumerator *
-playlist_open_any(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **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
deleted file mode 100644
index 743db2e39..000000000
--- a/src/PlaylistAny.hxx
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_ANY_HXX
-#define MPD_PLAYLIST_ANY_HXX
-
-class Mutex;
-class Cond;
-class SongEnumerator;
-struct InputStream;
-
-/**
- * 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
- */
-SongEnumerator *
-playlist_open_any(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **is_r);
-
-#endif
diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx
deleted file mode 100644
index b0ff03a7e..000000000
--- a/src/PlaylistControl.cxx
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Functions for controlling playback on the playlist level.
- *
- */
-
-#include "config.h"
-#include "Playlist.hxx"
-#include "PlaylistError.hxx"
-#include "PlayerControl.hxx"
-#include "Song.hxx"
-#include "Log.hxx"
-
-void
-playlist::Stop(PlayerControl &pc)
-{
- if (!playing)
- return;
-
- assert(current >= 0);
-
- FormatDebug(playlist_domain, "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);
- }
-}
-
-PlaylistResult
-playlist::PlayPosition(PlayerControl &pc, int song)
-{
- pc.ClearError();
-
- unsigned i = song;
- if (song == -1) {
- /* play any song ("current" song, or the first song */
-
- if (queue.IsEmpty())
- return PlaylistResult::SUCCESS;
-
- if (playing) {
- /* already playing: unpause playback, just in
- case it was paused, and return */
- pc.SetPause(false);
- return PlaylistResult::SUCCESS;
- }
-
- /* select a song: "current" song, or the first one */
- i = current >= 0
- ? current
- : 0;
- } else if (!queue.IsValidPosition(song))
- return PlaylistResult::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 PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::PlayId(PlayerControl &pc, int id)
-{
- if (id == -1)
- return PlayPosition(pc, id);
-
- int song = queue.IdToPosition(id);
- if (song < 0)
- return PlaylistResult::NO_SUCH_SONG;
-
- return PlayPosition(pc, song);
-}
-
-void
-playlist::PlayNext(PlayerControl &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 PlayOrder() will
- discard them anyway */
- }
-
- PlayOrder(pc, next_order);
- }
-
- /* Consume mode removes each played songs. */
- if (queue.consume)
- DeleteOrder(pc, old_current);
-}
-
-void
-playlist::PlayPrevious(PlayerControl &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);
-}
-
-PlaylistResult
-playlist::SeekSongOrder(PlayerControl &pc, unsigned i, float seek_time)
-{
- assert(queue.IsValidOrder(i));
-
- const Song *queued_song = GetQueuedSong();
-
- 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 PlaylistResult::NOT_PLAYING;
- }
-
- queued = -1;
- UpdateQueuedSong(pc, nullptr);
-
- return PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
-{
- if (!queue.IsValidPosition(song))
- return PlaylistResult::BAD_RANGE;
-
- unsigned i = queue.random
- ? queue.PositionToOrder(song)
- : song;
-
- return SeekSongOrder(pc, i, seek_time);
-}
-
-PlaylistResult
-playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time)
-{
- int song = queue.IdToPosition(id);
- if (song < 0)
- return PlaylistResult::NO_SUCH_SONG;
-
- return SeekSongPosition(pc, song, seek_time);
-}
-
-PlaylistResult
-playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative)
-{
- if (!playing)
- return PlaylistResult::NOT_PLAYING;
-
- if (relative) {
- const auto status = pc.GetStatus();
-
- if (status.state != PlayerState::PLAY &&
- status.state != PlayerState::PAUSE)
- return PlaylistResult::NOT_PLAYING;
-
- seek_time += (int)status.elapsed_time;
- }
-
- if (seek_time < 0)
- seek_time = 0;
-
- return SeekSongOrder(pc, current, seek_time);
-}
diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx
index a6d15e755..3421ecb02 100644
--- a/src/PlaylistDatabase.cxx
+++ b/src/PlaylistDatabase.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,8 +19,9 @@
#include "config.h"
#include "PlaylistDatabase.hxx"
-#include "PlaylistVector.hxx"
-#include "TextFile.hxx"
+#include "db/PlaylistVector.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -31,13 +32,13 @@
static constexpr Domain playlist_database_domain("playlist_database");
void
-playlist_vector_save(FILE *fp, const PlaylistVector &pv)
+playlist_vector_save(BufferedOutputStream &os, 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);
+ os.Format(PLAYLIST_META_BEGIN "%s\n"
+ "mtime: %li\n"
+ "playlist_end\n",
+ pi.name.c_str(), (long)pi.mtime);
}
bool
@@ -59,7 +60,7 @@ playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name,
}
*colon++ = 0;
- value = strchug_fast(colon);
+ value = StripLeft(colon);
if (strcmp(line, "mtime") == 0)
pm.mtime = strtol(value, nullptr, 10);
diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx
index 1481f621f..17f82f64b 100644
--- a/src/PlaylistDatabase.hxx
+++ b/src/PlaylistDatabase.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,16 +22,15 @@
#include "check.h"
-#include <stdio.h>
-
#define PLAYLIST_META_BEGIN "playlist_begin: "
class PlaylistVector;
+class BufferedOutputStream;
class TextFile;
class Error;
void
-playlist_vector_save(FILE *fp, const PlaylistVector &pv);
+playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
bool
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name,
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
deleted file mode 100644
index 0dffd3d80..000000000
--- a/src/PlaylistEdit.cxx
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Functions for editing the playlist (adding, removing, reordering
- * songs in the queue).
- *
- */
-
-#include "config.h"
-#include "Playlist.hxx"
-#include "PlaylistError.hxx"
-#include "PlayerControl.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
-#include "Song.hxx"
-#include "Idle.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabasePlugin.hxx"
-#include "Log.hxx"
-
-#include <stdlib.h>
-
-void
-playlist::OnModified()
-{
- if (bulk_edit) {
- /* postponed to CommitBulk() */
- bulk_modified = true;
- return;
- }
-
- queue.IncrementVersion();
-
- idle_add(IDLE_PLAYLIST);
-}
-
-void
-playlist::Clear(PlayerControl &pc)
-{
- Stop(pc);
-
- queue.Clear();
- current = -1;
-
- OnModified();
-}
-
-void
-playlist::BeginBulk()
-{
- assert(!bulk_edit);
-
- bulk_edit = true;
- bulk_modified = false;
-}
-
-void
-playlist::CommitBulk(PlayerControl &pc)
-{
- assert(bulk_edit);
-
- bulk_edit = false;
- if (!bulk_modified)
- return;
-
- if (queued < 0)
- /* if no song was queued, UpdateQueuedSong() is being
- ignored in "bulk" edit mode; now that we have
- shuffled all new songs, we can pick a random one
- (instead of always picking the first one that was
- added) */
- UpdateQueuedSong(pc, nullptr);
-
- OnModified();
-}
-
-PlaylistResult
-playlist::AppendFile(PlayerControl &pc,
- const char *path_utf8, unsigned *added_id)
-{
- Song *song = Song::LoadFile(path_utf8, nullptr);
- if (song == nullptr)
- return PlaylistResult::NO_SUCH_SONG;
-
- const auto result = AppendSong(pc, song, added_id);
- song->Free();
- return result;
-}
-
-PlaylistResult
-playlist::AppendSong(PlayerControl &pc,
- Song *song, unsigned *added_id)
-{
- unsigned id;
-
- if (queue.IsFull())
- return PlaylistResult::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 PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::AppendURI(PlayerControl &pc,
- const char *uri, unsigned *added_id)
-{
- FormatDebug(playlist_domain, "add to playlist: %s", uri);
-
- const Database *db = nullptr;
- Song *song;
- if (uri_has_scheme(uri)) {
- song = Song::NewRemote(uri);
- } else {
- db = GetDatabase();
- if (db == nullptr)
- return PlaylistResult::NO_SUCH_SONG;
-
- song = db->GetSong(uri, IgnoreError());
- if (song == nullptr)
- return PlaylistResult::NO_SUCH_SONG;
- }
-
- PlaylistResult result = AppendSong(pc, song, added_id);
- if (db != nullptr)
- db->ReturnSong(song);
- else
- song->Free();
-
- return result;
-}
-
-PlaylistResult
-playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2)
-{
- if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
- return PlaylistResult::BAD_RANGE;
-
- const Song *const queued_song = GetQueuedSong();
-
- 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 PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2)
-{
- int song1 = queue.IdToPosition(id1);
- int song2 = queue.IdToPosition(id2);
-
- if (song1 < 0 || song2 < 0)
- return PlaylistResult::NO_SUCH_SONG;
-
- return SwapPositions(pc, song1, song2);
-}
-
-PlaylistResult
-playlist::SetPriorityRange(PlayerControl &pc,
- unsigned start, unsigned end,
- uint8_t priority)
-{
- if (start >= GetLength())
- return PlaylistResult::BAD_RANGE;
-
- if (end > GetLength())
- end = GetLength();
-
- if (start >= end)
- return PlaylistResult::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 PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::SetPriorityId(PlayerControl &pc,
- unsigned song_id, uint8_t priority)
-{
- int song_position = queue.IdToPosition(song_id);
- if (song_position < 0)
- return PlaylistResult::NO_SUCH_SONG;
-
- return SetPriorityRange(pc, song_position, song_position + 1,
- priority);
-
-}
-
-void
-playlist::DeleteInternal(PlayerControl &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() == PlayerState::PAUSE;
-
- /* the current song is going to be deleted: 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 {
- /* stop the player */
-
- pc.Stop();
- playing = false;
- }
-
- *queued_p = nullptr;
- } 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--;
-}
-
-PlaylistResult
-playlist::DeletePosition(PlayerControl &pc, unsigned song)
-{
- if (song >= queue.GetLength())
- return PlaylistResult::BAD_RANGE;
-
- const Song *queued_song = GetQueuedSong();
-
- DeleteInternal(pc, song, &queued_song);
-
- UpdateQueuedSong(pc, queued_song);
- OnModified();
-
- return PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end)
-{
- if (start >= queue.GetLength())
- return PlaylistResult::BAD_RANGE;
-
- if (end > queue.GetLength())
- end = queue.GetLength();
-
- if (start >= end)
- return PlaylistResult::SUCCESS;
-
- const Song *queued_song = GetQueuedSong();
-
- do {
- DeleteInternal(pc, --end, &queued_song);
- } while (end != start);
-
- UpdateQueuedSong(pc, queued_song);
- OnModified();
-
- return PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::DeleteId(PlayerControl &pc, unsigned id)
-{
- int song = queue.IdToPosition(id);
- if (song < 0)
- return PlaylistResult::NO_SUCH_SONG;
-
- return DeletePosition(pc, song);
-}
-
-void
-playlist::DeleteSong(PlayerControl &pc, const struct Song &song)
-{
- for (int i = queue.GetLength() - 1; i >= 0; --i)
- if (SongEquals(song, queue.Get(i)))
- DeletePosition(pc, i);
-}
-
-PlaylistResult
-playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
-{
- if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1))
- return PlaylistResult::BAD_RANGE;
-
- if ((to >= 0 && to + end - start - 1 >= GetLength()) ||
- (to < 0 && unsigned(abs(to)) > GetLength()))
- return PlaylistResult::BAD_RANGE;
-
- if ((int)start == to)
- /* nothing happens */
- return PlaylistResult::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 PlaylistResult::BAD_RANGE;
-
- if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
- /* no-op, can't be moved to offset of itself */
- return PlaylistResult::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 PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist::MoveId(PlayerControl &pc, unsigned id1, int to)
-{
- int song = queue.IdToPosition(id1);
- if (song < 0)
- return PlaylistResult::NO_SUCH_SONG;
-
- return MoveRange(pc, song, song + 1, to);
-}
-
-void
-playlist::Shuffle(PlayerControl &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/PlaylistError.cxx b/src/PlaylistError.cxx
index 91291f551..085246f15 100644
--- a/src/PlaylistError.cxx
+++ b/src/PlaylistError.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/PlaylistError.hxx b/src/PlaylistError.hxx
index 8ea65ca34..0f2424f41 100644
--- a/src/PlaylistError.hxx
+++ b/src/PlaylistError.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
index e5285ad04..ab269378a 100644
--- a/src/PlaylistFile.cxx
+++ b/src/PlaylistFile.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,16 +20,15 @@
#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 "db/PlaylistInfo.hxx"
+#include "db/PlaylistVector.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
#include "Mapper.hxx"
-#include "TextFile.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "ConfigDefaults.hxx"
+#include "fs/io/TextFile.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "config/ConfigDefaults.hxx"
#include "Idle.hxx"
#include "fs/Limits.hxx"
#include "fs/AllocatedPath.hxx"
@@ -37,16 +36,12 @@
#include "fs/Charset.hxx"
#include "fs/FileSystem.hxx"
#include "fs/DirectoryReader.hxx"
+#include "util/StringUtil.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.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 <errno.h>
@@ -125,6 +120,29 @@ spl_map_to_fs(const char *name_utf8, Error &error)
return path_fs;
}
+gcc_pure
+static bool
+IsNotFoundError(const Error &error)
+{
+#ifdef WIN32
+ return error.IsDomain(win32_domain) &&
+ error.GetCode() == ERROR_FILE_NOT_FOUND;
+#else
+ return error.IsDomain(errno_domain) &&
+ error.GetCode() == ENOENT;
+#endif
+}
+
+static void
+TranslatePlaylistError(Error &error)
+{
+ if (IsNotFoundError(error)) {
+ error.Clear();
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_LIST),
+ "No such playlist");
+ }
+}
+
/**
* Create an #Error for the current errno.
*/
@@ -145,8 +163,8 @@ playlist_errno(Error &error)
static bool
LoadPlaylistFileInfo(PlaylistInfo &info,
- const AllocatedPath &parent_path_fs,
- const AllocatedPath &name_fs)
+ 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);
@@ -155,7 +173,7 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
memchr(name_fs_str, '\n', name_length) != nullptr)
return false;
- if (!g_str_has_suffix(name_fs_str, PLAYLIST_FILE_SUFFIX))
+ if (!StringEndsWith(name_fs_str, PLAYLIST_FILE_SUFFIX))
return false;
const auto path_fs = AllocatedPath::Build(parent_path_fs, name_fs);
@@ -163,10 +181,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode))
return false;
- char *name = g_strndup(name_fs_str,
- name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
- std::string name_utf8 = PathToUTF8(name);
- g_free(name);
+ std::string name(name_fs_str,
+ name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX));
+ std::string name_utf8 = PathToUTF8(name.c_str());
if (name_utf8.empty())
return false;
@@ -238,9 +255,9 @@ LoadPlaylistFile(const char *utf8path, Error &error)
if (path_fs.IsNull())
return contents;
- TextFile file(path_fs);
+ TextFile file(path_fs, error);
if (file.HasFailed()) {
- playlist_errno(error);
+ TranslatePlaylistError(error);
return contents;
}
@@ -252,9 +269,10 @@ LoadPlaylistFile(const char *utf8path, Error &error)
std::string uri_utf8;
if (!uri_has_scheme(s)) {
+#ifdef ENABLE_DATABASE
uri_utf8 = map_fs_to_utf8(s);
if (uri_utf8.empty()) {
- if (PathTraits::IsAbsoluteFS(s)) {
+ if (PathTraitsFS::IsAbsolute(s)) {
uri_utf8 = PathToUTF8(s);
if (uri_utf8.empty())
continue;
@@ -263,6 +281,9 @@ LoadPlaylistFile(const char *utf8path, Error &error)
} else
continue;
}
+#else
+ continue;
+#endif
} else {
uri_utf8 = PathToUTF8(s);
if (uri_utf8.empty())
@@ -369,7 +390,7 @@ spl_remove_index(const char *utf8path, unsigned pos, Error &error)
}
bool
-spl_append_song(const char *utf8path, const Song &song, Error &error)
+spl_append_song(const char *utf8path, const DetachedSong &song, Error &error)
{
if (spl_map(error).IsNull())
return false;
@@ -407,26 +428,17 @@ spl_append_song(const char *utf8path, const Song &song, Error &error)
}
bool
-spl_append_uri(const char *url, const char *utf8file, Error &error)
+spl_append_uri(const char *utf8file,
+ const SongLoader &loader, const char *url,
+ Error &error)
{
- if (uri_has_scheme(url)) {
- Song *song = Song::NewRemote(url);
- bool success = spl_append_song(utf8file, *song, error);
- song->Free();
- return success;
- } else {
- const Database *db = GetDatabase(error);
- if (db == nullptr)
- return false;
-
- Song *song = db->GetSong(url, error);
- if (song == nullptr)
- return false;
-
- bool success = spl_append_song(utf8file, *song, error);
- db->ReturnSong(song);
- return success;
- }
+ DetachedSong *song = loader.LoadSong(url, error);
+ if (song == nullptr)
+ return false;
+
+ bool success = spl_append_song(utf8file, *song, error);
+ delete song;
+ return success;
}
static bool
diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx
index f04530bcc..7154b1f84 100644
--- a/src/PlaylistFile.hxx
+++ b/src/PlaylistFile.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,8 +23,8 @@
#include <vector>
#include <string>
-struct Song;
-struct PlaylistInfo;
+class DetachedSong;
+class SongLoader;
class PlaylistVector;
class Error;
@@ -69,10 +69,12 @@ bool
spl_remove_index(const char *utf8path, unsigned pos, Error &error);
bool
-spl_append_song(const char *utf8path, const Song &song, Error &error);
+spl_append_song(const char *utf8path, const DetachedSong &song, Error &error);
bool
-spl_append_uri(const char *file, const char *utf8file, Error &error);
+spl_append_uri(const char *path_utf8,
+ const SongLoader &loader, const char *uri_utf8,
+ Error &error);
bool
spl_rename(const char *utf8from, const char *utf8to, Error &error);
diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx
index 97902275b..dacfad0c7 100644
--- a/src/PlaylistGlobal.cxx
+++ b/src/PlaylistGlobal.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,7 +24,6 @@
#include "config.h"
#include "PlaylistGlobal.hxx"
-#include "Playlist.hxx"
#include "Main.hxx"
#include "Instance.hxx"
#include "GlobalEvents.hxx"
diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx
index 4397292db..a2e3bb030 100644
--- a/src/PlaylistGlobal.hxx
+++ b/src/PlaylistGlobal.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx
deleted file mode 100644
index 2c5b9ae1a..000000000
--- a/src/PlaylistInfo.hxx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_INFO_HXX
-#define MPD_PLAYLIST_INFO_HXX
-
-#include "check.h"
-#include "Compiler.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
deleted file mode 100644
index 68ac22438..000000000
--- a/src/PlaylistMapper.cxx
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlaylistMapper.hxx"
-#include "PlaylistFile.hxx"
-#include "PlaylistRegistry.hxx"
-#include "Mapper.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "util/UriUtil.hxx"
-
-#include <assert.h>
-
-static SongEnumerator *
-playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
- InputStream **is_r)
-{
- auto playlist = playlist_list_open_uri(path_fs, mutex, cond);
- if (playlist != nullptr)
- *is_r = nullptr;
- else
- playlist = playlist_list_open_path(path_fs, mutex, cond, is_r);
-
- return playlist;
-}
-
-/**
- * Load a playlist from the configured playlist directory.
- */
-static SongEnumerator *
-playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **is_r)
-{
- assert(spl_valid_name(uri));
-
- const auto &playlist_directory_fs = map_spl_path();
- if (playlist_directory_fs.IsNull())
- return nullptr;
-
- const auto uri_fs = AllocatedPath::FromUTF8(uri);
- if (uri_fs.IsNull())
- return nullptr;
-
- const auto path_fs =
- AllocatedPath::Build(playlist_directory_fs, uri_fs);
- assert(!path_fs.IsNull());
-
- return playlist_open_path(path_fs.c_str(), mutex, cond, is_r);
-}
-
-/**
- * Load a playlist from the configured music directory.
- */
-static SongEnumerator *
-playlist_open_in_music_dir(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **is_r)
-{
- assert(uri_safe_local(uri));
-
- const auto path = map_uri_fs(uri);
- if (path.IsNull())
- return nullptr;
-
- return playlist_open_path(path.c_str(), mutex, cond, is_r);
-}
-
-SongEnumerator *
-playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **is_r)
-{
- if (spl_valid_name(uri)) {
- auto playlist = playlist_open_in_playlist_dir(uri, mutex, cond,
- is_r);
- if (playlist != nullptr)
- return playlist;
- }
-
- if (uri_safe_local(uri)) {
- auto playlist = playlist_open_in_music_dir(uri, mutex, cond,
- is_r);
- if (playlist != nullptr)
- return playlist;
- }
-
- return nullptr;
-}
diff --git a/src/PlaylistMapper.hxx b/src/PlaylistMapper.hxx
deleted file mode 100644
index 7132415f1..000000000
--- a/src/PlaylistMapper.hxx
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_MAPPER_HXX
-#define MPD_PLAYLIST_MAPPER_HXX
-
-class Mutex;
-class Cond;
-class SongEnumerator;
-struct InputStream;
-
-/**
- * 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
- */
-SongEnumerator *
-playlist_mapper_open(const char *uri, Mutex &mutex, Cond &cond,
- InputStream **is_r);
-
-#endif
diff --git a/src/PlaylistPlugin.hxx b/src/PlaylistPlugin.hxx
deleted file mode 100644
index 6eb0f4771..000000000
--- a/src/PlaylistPlugin.hxx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_PLUGIN_HXX
-#define MPD_PLAYLIST_PLUGIN_HXX
-
-struct config_param;
-struct InputStream;
-struct Tag;
-class Mutex;
-class Cond;
-class SongEnumerator;
-
-struct playlist_plugin {
- const char *name;
-
- /**
- * Initialize the 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 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.
- */
- SongEnumerator *(*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.
- */
- SongEnumerator *(*open_stream)(InputStream &is);
-
- 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 nullptr 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 != nullptr
- ? 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 != nullptr)
- plugin->finish();
-}
-
-static inline SongEnumerator *
-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 SongEnumerator *
-playlist_plugin_open_stream(const struct playlist_plugin *plugin,
- InputStream &is)
-{
- return plugin->open_stream(is);
-}
-
-#endif
diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx
index e3d500be3..cfa56c7b3 100644
--- a/src/PlaylistPrint.cxx
+++ b/src/PlaylistPrint.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,23 +20,22 @@
#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 "SongEnumerator.hxx"
+#include "queue/Playlist.hxx"
+#include "queue/QueuePrint.hxx"
#include "SongPrint.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabasePlugin.hxx"
-#include "Client.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "db/Interface.hxx"
+#include "client/Client.hxx"
+#include "input/InputStream.hxx"
+#include "DetachedSong.hxx"
#include "fs/Traits.hxx"
#include "util/Error.hxx"
#include "thread/Cond.hxx"
+#define SONG_FILE "file: "
+#define SONG_TIME "Time: "
+
void
playlist_print_uris(Client &client, const playlist &playlist)
{
@@ -112,14 +111,16 @@ playlist_print_changes_position(Client &client,
queue_print_changes_position(client, playlist.queue, version);
}
+#ifdef ENABLE_DATABASE
+
static bool
PrintSongDetails(Client &client, const char *uri_utf8)
{
- const Database *db = GetDatabase();
+ const Database *db = client.partition.instance.database;
if (db == nullptr)
return false;
- Song *song = db->GetSong(uri_utf8, IgnoreError());
+ auto *song = db->GetSong(uri_utf8, IgnoreError());
if (song == nullptr)
return false;
@@ -128,63 +129,27 @@ PrintSongDetails(Client &client, const char *uri_utf8)
return true;
}
+#endif
+
bool
spl_print(Client &client, const char *name_utf8, bool detail,
Error &error)
{
+#ifndef ENABLE_DATABASE
+ (void)detail;
+#endif
+
PlaylistFileContents contents = LoadPlaylistFile(name_utf8, error);
if (contents.empty() && error.IsDefined())
return false;
for (const auto &uri_utf8 : contents) {
+#ifdef ENABLE_DATABASE
if (!detail || !PrintSongDetails(client, uri_utf8.c_str()))
+#endif
client_printf(client, SONG_FILE "%s\n",
uri_utf8.c_str());
}
return true;
}
-
-static void
-playlist_provider_print(Client &client, const char *uri,
- SongEnumerator &e, bool detail)
-{
- const std::string base_uri = uri != nullptr
- ? PathTraits::GetParentUTF8(uri)
- : std::string(".");
-
- Song *song;
- while ((song = e.NextSong()) != nullptr) {
- song = playlist_check_translate_song(song, base_uri.c_str(),
- false);
- if (song == nullptr)
- continue;
-
- if (detail)
- song_print_info(client, *song);
- else
- song_print_uri(client, *song);
-
- song->Free();
- }
-}
-
-bool
-playlist_file_print(Client &client, const char *uri, bool detail)
-{
- Mutex mutex;
- Cond cond;
-
- InputStream *is;
- SongEnumerator *playlist = playlist_open_any(uri, mutex, cond, &is);
- if (playlist == nullptr)
- return false;
-
- playlist_provider_print(client, uri, *playlist, detail);
- delete playlist;
-
- if (is != nullptr)
- is->Close();
-
- return true;
-}
diff --git a/src/PlaylistPrint.hxx b/src/PlaylistPrint.hxx
index 9cbc46325..38a4cc7cf 100644
--- a/src/PlaylistPrint.hxx
+++ b/src/PlaylistPrint.hxx
@@ -1,6 +1,5 @@
-
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -96,15 +95,4 @@ bool
spl_print(Client &client, const char *name_utf8, bool detail,
Error &error);
-/**
- * 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
deleted file mode 100644
index a8359d427..000000000
--- a/src/PlaylistQueue.cxx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlaylistQueue.hxx"
-#include "PlaylistPlugin.hxx"
-#include "PlaylistAny.hxx"
-#include "PlaylistSong.hxx"
-#include "Playlist.hxx"
-#include "InputStream.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
-#include "thread/Cond.hxx"
-#include "fs/Traits.hxx"
-
-PlaylistResult
-playlist_load_into_queue(const char *uri, SongEnumerator &e,
- unsigned start_index, unsigned end_index,
- playlist &dest, PlayerControl &pc,
- bool secure)
-{
- const std::string base_uri = uri != nullptr
- ? PathTraits::GetParentUTF8(uri)
- : std::string(".");
-
- Song *song;
- for (unsigned i = 0;
- i < end_index && (song = e.NextSong()) != nullptr;
- ++i) {
- if (i < start_index) {
- /* skip songs before the start index */
- song->Free();
- continue;
- }
-
- song = playlist_check_translate_song(song, base_uri.c_str(),
- secure);
- if (song == nullptr)
- continue;
-
- PlaylistResult result = dest.AppendSong(pc, song);
- song->Free();
- if (result != PlaylistResult::SUCCESS)
- return result;
- }
-
- return PlaylistResult::SUCCESS;
-}
-
-PlaylistResult
-playlist_open_into_queue(const char *uri,
- unsigned start_index, unsigned end_index,
- playlist &dest, PlayerControl &pc,
- bool secure)
-{
- Mutex mutex;
- Cond cond;
-
- InputStream *is;
- auto playlist = playlist_open_any(uri, mutex, cond, &is);
- if (playlist == nullptr)
- return PlaylistResult::NO_SUCH_LIST;
-
- PlaylistResult result =
- playlist_load_into_queue(uri, *playlist,
- start_index, end_index,
- dest, pc, secure);
- delete playlist;
-
- if (is != nullptr)
- is->Close();
-
- return result;
-}
diff --git a/src/PlaylistQueue.hxx b/src/PlaylistQueue.hxx
deleted file mode 100644
index fc566ec78..000000000
--- a/src/PlaylistQueue.hxx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*! \file
- * \brief Glue between playlist plugin and the play queue
- */
-
-#ifndef MPD_PLAYLIST_QUEUE_HXX
-#define MPD_PLAYLIST_QUEUE_HXX
-
-#include "PlaylistError.hxx"
-
-class SongEnumerator;
-struct playlist;
-struct PlayerControl;
-
-/**
- * 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)
- */
-PlaylistResult
-playlist_load_into_queue(const char *uri, SongEnumerator &e,
- unsigned start_index, unsigned end_index,
- playlist &dest, PlayerControl &pc,
- bool secure);
-
-/**
- * Opens a playlist with a playlist plugin and append to the specified
- * play queue.
- */
-PlaylistResult
-playlist_open_into_queue(const char *uri,
- unsigned start_index, unsigned end_index,
- playlist &dest, PlayerControl &pc,
- bool secure);
-
-#endif
-
diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx
deleted file mode 100644
index f81978322..000000000
--- a/src/PlaylistRegistry.cxx
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlaylistRegistry.hxx"
-#include "PlaylistPlugin.hxx"
-#include "playlist/ExtM3uPlaylistPlugin.hxx"
-#include "playlist/M3uPlaylistPlugin.hxx"
-#include "playlist/XspfPlaylistPlugin.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 "InputStream.hxx"
-#include "util/UriUtil.hxx"
-#include "util/StringUtil.hxx"
-#include "util/Error.hxx"
-#include "util/Macros.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-const struct playlist_plugin *const playlist_plugins[] = {
- &extm3u_playlist_plugin,
- &m3u_playlist_plugin,
- &xspf_playlist_plugin,
- &pls_playlist_plugin,
- &asx_playlist_plugin,
- &rss_playlist_plugin,
-#ifdef ENABLE_DESPOTIFY
- &despotify_playlist_plugin,
-#endif
-#ifdef ENABLE_SOUNDCLOUD
- &soundcloud_playlist_plugin,
-#endif
- &cue_playlist_plugin,
- &embcue_playlist_plugin,
- nullptr
-};
-
-static constexpr unsigned n_playlist_plugins =
- ARRAY_SIZE(playlist_plugins) - 1;
-
-/** which plugins have been initialized successfully? */
-static bool playlist_plugins_enabled[n_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 nullptr if none was configured
- */
-static const struct config_param *
-playlist_plugin_config(const char *plugin_name)
-{
- const struct config_param *param = nullptr;
-
- assert(plugin_name != nullptr);
-
- while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != nullptr) {
- const char *name = param->GetBlockValue("name");
- if (name == nullptr)
- FormatFatalError("playlist configuration without 'plugin' name in line %d",
- param->line);
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return nullptr;
-}
-
-void
-playlist_list_global_init(void)
-{
- const config_param empty;
-
- for (unsigned i = 0; playlist_plugins[i] != nullptr; ++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 SongEnumerator *
-playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond,
- bool *tried)
-{
- char *scheme;
- SongEnumerator *playlist = nullptr;
-
- assert(uri != nullptr);
-
- scheme = g_uri_parse_scheme(uri);
- if (scheme == nullptr)
- return nullptr;
-
- for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
- const struct playlist_plugin *plugin = playlist_plugins[i];
-
- assert(!tried[i]);
-
- if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr &&
- plugin->schemes != nullptr &&
- string_array_contains(plugin->schemes, scheme)) {
- playlist = playlist_plugin_open_uri(plugin, uri,
- mutex, cond);
- if (playlist != nullptr)
- break;
-
- tried[i] = true;
- }
- }
-
- g_free(scheme);
- return playlist;
-}
-
-static SongEnumerator *
-playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
- const bool *tried)
-{
- SongEnumerator *playlist = nullptr;
-
- assert(uri != nullptr);
-
- UriSuffixBuffer suffix_buffer;
- const char *const suffix = uri_get_suffix(uri, suffix_buffer);
- if (suffix == nullptr)
- return nullptr;
-
- for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
- const struct playlist_plugin *plugin = playlist_plugins[i];
-
- if (playlist_plugins_enabled[i] && !tried[i] &&
- plugin->open_uri != nullptr && plugin->suffixes != nullptr &&
- string_array_contains(plugin->suffixes, suffix)) {
- playlist = playlist_plugin_open_uri(plugin, uri,
- mutex, cond);
- if (playlist != nullptr)
- break;
- }
- }
-
- return playlist;
-}
-
-SongEnumerator *
-playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond)
-{
- /** this array tracks which plugins have already been tried by
- playlist_list_open_uri_scheme() */
- bool tried[n_playlist_plugins];
-
- assert(uri != nullptr);
-
- memset(tried, false, sizeof(tried));
-
- auto playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried);
- if (playlist == nullptr)
- playlist = playlist_list_open_uri_suffix(uri, mutex, cond,
- tried);
-
- return playlist;
-}
-
-static SongEnumerator *
-playlist_list_open_stream_mime2(InputStream &is, const char *mime)
-{
- assert(mime != nullptr);
-
- playlist_plugins_for_each_enabled(plugin) {
- if (plugin->open_stream != nullptr &&
- plugin->mime_types != nullptr &&
- string_array_contains(plugin->mime_types, mime)) {
- /* rewind the stream, so each plugin gets a
- fresh start */
- is.Rewind(IgnoreError());
-
- auto playlist = playlist_plugin_open_stream(plugin,
- is);
- if (playlist != nullptr)
- return playlist;
- }
- }
-
- return nullptr;
-}
-
-static SongEnumerator *
-playlist_list_open_stream_mime(InputStream &is, const char *full_mime)
-{
- assert(full_mime != nullptr);
-
- const char *semicolon = strchr(full_mime, ';');
- if (semicolon == nullptr)
- return playlist_list_open_stream_mime2(is, full_mime);
-
- if (semicolon == full_mime)
- return nullptr;
-
- /* probe only the portion before the semicolon*/
- const std::string mime(full_mime, semicolon);
- return playlist_list_open_stream_mime2(is, mime.c_str());
-}
-
-static SongEnumerator *
-playlist_list_open_stream_suffix(InputStream &is, const char *suffix)
-{
- assert(suffix != nullptr);
-
- playlist_plugins_for_each_enabled(plugin) {
- if (plugin->open_stream != nullptr &&
- plugin->suffixes != nullptr &&
- string_array_contains(plugin->suffixes, suffix)) {
- /* rewind the stream, so each plugin gets a
- fresh start */
- is.Rewind(IgnoreError());
-
- auto playlist = playlist_plugin_open_stream(plugin, is);
- if (playlist != nullptr)
- return playlist;
- }
- }
-
- return nullptr;
-}
-
-SongEnumerator *
-playlist_list_open_stream(InputStream &is, const char *uri)
-{
- is.LockWaitReady();
-
- const char *const mime = is.GetMimeType();
- if (mime != nullptr) {
- auto playlist = playlist_list_open_stream_mime(is, mime);
- if (playlist != nullptr)
- return playlist;
- }
-
- UriSuffixBuffer suffix_buffer;
- const char *suffix = uri != nullptr
- ? uri_get_suffix(uri, suffix_buffer)
- : nullptr;
- if (suffix != nullptr) {
- auto playlist = playlist_list_open_stream_suffix(is, suffix);
- if (playlist != nullptr)
- return playlist;
- }
-
- return nullptr;
-}
-
-bool
-playlist_suffix_supported(const char *suffix)
-{
- assert(suffix != nullptr);
-
- playlist_plugins_for_each_enabled(plugin) {
- if (plugin->suffixes != nullptr &&
- string_array_contains(plugin->suffixes, suffix))
- return true;
- }
-
- return false;
-}
-
-SongEnumerator *
-playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
- InputStream **is_r)
-{
- const char *suffix;
-
- assert(path_fs != nullptr);
-
- suffix = uri_get_suffix(path_fs);
- if (suffix == nullptr || !playlist_suffix_supported(suffix))
- return nullptr;
-
- Error error;
- InputStream *is = InputStream::Open(path_fs, mutex, cond, error);
- if (is == nullptr) {
- if (error.IsDefined())
- LogError(error);
-
- return nullptr;
- }
-
- is->LockWaitReady();
-
- auto playlist = playlist_list_open_stream_suffix(*is, suffix);
- if (playlist != nullptr)
- *is_r = is;
- else
- is->Close();
-
- return playlist;
-}
diff --git a/src/PlaylistRegistry.hxx b/src/PlaylistRegistry.hxx
deleted file mode 100644
index 2b747316d..000000000
--- a/src/PlaylistRegistry.hxx
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_REGISTRY_HXX
-#define MPD_PLAYLIST_REGISTRY_HXX
-
-class Mutex;
-class Cond;
-class SongEnumerator;
-struct InputStream;
-
-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) != nullptr; \
- ++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.
- */
-SongEnumerator *
-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
- */
-SongEnumerator *
-playlist_list_open_stream(InputStream &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 nullptr on error
- */
-SongEnumerator *
-playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
- InputStream **is_r);
-
-#endif
diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx
index 29bf193fd..67f05267f 100644
--- a/src/PlaylistSave.cxx
+++ b/src/PlaylistSave.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,44 +21,44 @@
#include "PlaylistSave.hxx"
#include "PlaylistFile.hxx"
#include "PlaylistError.hxx"
-#include "Playlist.hxx"
-#include "Song.hxx"
+#include "queue/Playlist.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
#include "Mapper.hxx"
#include "Idle.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
+#include "util/Alloc.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <string.h>
void
-playlist_print_song(FILE *file, const Song &song)
+playlist_print_song(FILE *file, const DetachedSong &song)
{
- if (playlist_saveAbsolutePaths && song.IsInDatabase()) {
- const auto path = map_song_fs(song);
- if (!path.IsNull())
- fprintf(file, "%s\n", path.c_str());
- } else {
- const auto uri_utf8 = song.GetURI();
- const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8.c_str());
-
- if (!uri_fs.IsNull())
- fprintf(file, "%s\n", uri_fs.c_str());
- }
+ const char *uri_utf8 = playlist_saveAbsolutePaths
+ ? song.GetRealURI()
+ : song.GetURI();
+
+ const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8);
+ if (!uri_fs.IsNull())
+ fprintf(file, "%s\n", uri_fs.c_str());
}
void
playlist_print_uri(FILE *file, const char *uri)
{
- auto path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- !PathTraits::IsAbsoluteUTF8(uri)
+ auto path =
+#ifdef ENABLE_DATABASE
+ playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
+ !PathTraitsUTF8::IsAbsolute(uri)
? map_uri_fs(uri)
- : AllocatedPath::FromUTF8(uri);
+ :
+#endif
+ AllocatedPath::FromUTF8(uri);
if (!path.IsNull())
fprintf(file, "%s\n", path.c_str());
@@ -99,49 +99,3 @@ spl_save_playlist(const char *name_utf8, const playlist &playlist)
{
return spl_save_queue(name_utf8, playlist.queue);
}
-
-bool
-playlist_load_spl(struct playlist &playlist, PlayerControl &pc,
- const char *name_utf8,
- unsigned start_index, unsigned end_index,
- Error &error)
-{
- PlaylistFileContents contents = LoadPlaylistFile(name_utf8, error);
- if (contents.empty() && error.IsDefined())
- 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 (memcmp(uri_utf8.c_str(), "file:///", 8) == 0) {
- const char *path_utf8 = uri_utf8.c_str() + 7;
-
- if (playlist.AppendFile(pc, path_utf8) != PlaylistResult::SUCCESS)
- FormatError(playlist_domain,
- "can't add file \"%s\"", path_utf8);
- continue;
- }
-
- if ((playlist.AppendURI(pc, uri_utf8.c_str())) != PlaylistResult::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) != PlaylistResult::SUCCESS)
- FormatError(playlist_domain,
- "can't add file \"%s\"", temp2);
-
- g_free(temp2);
- }
- }
-
- return true;
-}
diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx
index 71e0a8189..914c8c086 100644
--- a/src/PlaylistSave.hxx
+++ b/src/PlaylistSave.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,14 +24,14 @@
#include <stdio.h>
-struct Song;
struct Queue;
struct playlist;
struct PlayerControl;
+class DetachedSong;
class Error;
void
-playlist_print_song(FILE *fp, const Song &song);
+playlist_print_song(FILE *file, const DetachedSong &song);
void
playlist_print_uri(FILE *fp, const char *uri);
@@ -48,14 +48,4 @@ spl_save_queue(const char *name_utf8, const Queue &queue);
PlaylistResult
spl_save_playlist(const char *name_utf8, const playlist &playlist);
-/**
- * Loads a stored playlist file, and append all songs to the global
- * playlist.
- */
-bool
-playlist_load_spl(struct playlist &playlist, PlayerControl &pc,
- const char *name_utf8,
- unsigned start_index, unsigned end_index,
- Error &error);
-
#endif
diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx
deleted file mode 100644
index 60774dc36..000000000
--- a/src/PlaylistSong.cxx
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlaylistSong.hxx"
-#include "Mapper.hxx"
-#include "DatabasePlugin.hxx"
-#include "DatabaseGlue.hxx"
-#include "ls.hxx"
-#include "tag/Tag.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/Traits.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.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 != nullptr
- ? (add->tag != nullptr
- ? Tag::Merge(*base->tag, *add->tag)
- : new Tag(*base->tag))
- : (add->tag != nullptr
- ? new Tag(*add->tag)
- : nullptr);
-
- dest->mtime = base->mtime;
- dest->start_ms = add->start_ms;
- dest->end_ms = add->end_ms;
-}
-
-static Song *
-apply_song_metadata(Song *dest, const Song *src)
-{
- Song *tmp;
-
- assert(dest != nullptr);
- assert(src != nullptr);
-
- if (src->tag == nullptr && src->start_ms == 0 && src->end_ms == 0)
- return dest;
-
- if (dest->IsInDatabase()) {
- const auto 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(), nullptr);
-
- merge_song_metadata(tmp, dest, src);
- } else {
- tmp = Song::NewFile(dest->uri, nullptr);
- merge_song_metadata(tmp, dest, src);
- }
-
- if (dest->tag != nullptr && dest->tag->time > 0 &&
- src->start_ms > 0 && src->end_ms == 0 &&
- src->start_ms / 1000 < (unsigned)dest->tag->time)
- /* 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 (PathTraits::IsAbsoluteUTF8(uri) && secure) {
- dest = Song::LoadFile(uri, nullptr);
- if (dest == nullptr)
- return nullptr;
- } else {
- const Database *db = GetDatabase();
- if (db == nullptr)
- return nullptr;
-
- Song *tmp = db->GetSong(uri, IgnoreError());
- if (tmp == nullptr)
- /* not found in database */
- return nullptr;
-
- 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 nullptr;
- }
- }
-
- if (base_uri != nullptr && strcmp(base_uri, ".") == 0)
- /* PathTraits::GetParentUTF8() returns "." when there
- is no directory name in the given path; clear that
- now, because it would break the database lookup
- functions */
- base_uri = nullptr;
-
- if (PathTraits::IsAbsoluteUTF8(uri)) {
- /* XXX fs_charset vs utf8? */
- const char *suffix = map_to_relative_path(uri);
- assert(suffix != nullptr);
-
- if (suffix != uri)
- uri = suffix;
- else if (!secure) {
- /* local files must be relative to the music
- directory when "secure" is enabled */
- song->Free();
- return nullptr;
- }
-
- base_uri = nullptr;
- }
-
- char *allocated = nullptr;
- if (base_uri != nullptr)
- uri = allocated = g_build_filename(base_uri, uri, nullptr);
-
- 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
deleted file mode 100644
index d0db99868..000000000
--- a/src/PlaylistSong.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_SONG_HXX
-#define MPD_PLAYLIST_SONG_HXX
-
-struct Song;
-
-/**
- * Verifies the song, returns nullptr 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
deleted file mode 100644
index ac2deebbf..000000000
--- a/src/PlaylistState.cxx
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Saving and loading the playlist to/from the state file.
- *
- */
-
-#include "config.h"
-#include "PlaylistState.hxx"
-#include "PlaylistError.hxx"
-#include "Playlist.hxx"
-#include "QueueSave.hxx"
-#include "TextFile.hxx"
-#include "PlayerControl.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "fs/Limits.hxx"
-#include "util/CharUtil.hxx"
-#include "Log.hxx"
-
-#include <glib.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,
- PlayerControl &pc)
-{
- const auto player_status = pc.GetStatus();
-
- fputs(PLAYLIST_STATE_FILE_STATE, fp);
-
- if (playlist.playing) {
- switch (player_status.state) {
- case PlayerState::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 == nullptr) {
- LogWarning(playlist_domain, "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 == nullptr) {
- LogWarning(playlist_domain,
- "'" 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, PlayerControl &pc)
-{
- int current = -1;
- int seek_time = 0;
- bool random_mode = false;
-
- if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
- return false;
-
- line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
-
- PlayerState state;
- if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
- state = PlayerState::PLAY;
- else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
- state = PlayerState::PAUSE;
- else
- state = PlayerState::STOP;
-
- while ((line = file.ReadLine()) != nullptr) {
- 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)) {
- const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY);
-
- /* this check discards "nan" which was used
- prior to MPD 0.18 */
- if (IsDigitASCII(*p))
- pc.SetMixRampDelay(atof(p));
- } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) {
- 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 == PlayerState::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 = PlayerState::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 != PlayerState::STOP)
- pc.UpdateAudio();
-
- if (state == PlayerState::STOP /* && config_option */)
- playlist.current = current;
- else if (seek_time == 0)
- playlist.PlayPosition(pc, current);
- else
- playlist.SeekSongPosition(pc, current, seek_time);
-
- if (state == PlayerState::PAUSE)
- pc.Pause();
- }
-
- return true;
-}
-
-unsigned
-playlist_state_get_hash(const playlist &playlist,
- PlayerControl &pc)
-{
- const auto player_status = pc.GetStatus();
-
- return playlist.queue.version ^
- (player_status.state != PlayerState::STOP
- ? ((int)player_status.elapsed_time << 8)
- : 0) ^
- (playlist.current >= 0
- ? (playlist.queue.OrderToPosition(playlist.current) << 16)
- : 0) ^
- ((int)pc.GetCrossFade() << 20) ^
- (unsigned(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
deleted file mode 100644
index 01cc94d03..000000000
--- a/src/PlaylistState.hxx
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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 PlayerControl;
-class TextFile;
-
-void
-playlist_state_save(FILE *fp, const struct playlist &playlist,
- PlayerControl &pc);
-
-bool
-playlist_state_restore(const char *line, TextFile &file,
- struct playlist &playlist, PlayerControl &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,
- PlayerControl &c);
-
-#endif
diff --git a/src/PlaylistUpdate.cxx b/src/PlaylistUpdate.cxx
deleted file mode 100644
index 0e72ef671..000000000
--- a/src/PlaylistUpdate.cxx
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Playlist.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabasePlugin.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "Idle.hxx"
-#include "util/Error.hxx"
-
-static bool
-UpdatePlaylistSong(const Database &db, Song &song)
-{
- if (!song.IsInDatabase() || !song.IsDetached())
- /* only update Songs instances that are "detached"
- from the Database */
- return false;
-
- Song *original = db.GetSong(song.uri, IgnoreError());
- if (original == nullptr)
- /* not found - shouldn't happen, because the update
- thread should ensure that all stale Song instances
- have been purged */
- return false;
-
- if (original->mtime == song.mtime) {
- /* not modified */
- db.ReturnSong(original);
- return false;
- }
-
- song.mtime = original->mtime;
-
- if (original->tag != nullptr)
- song.ReplaceTag(Tag(*original->tag));
-
- db.ReturnSong(original);
- return true;
-}
-
-void
-playlist::DatabaseModified()
-{
- const Database *db = GetDatabase();
- if (db == nullptr)
- /* how can this ever happen? */
- return;
-
- bool modified = false;
-
- for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) {
- if (UpdatePlaylistSong(*db, queue.Get(i))) {
- queue.ModifyAtPosition(i);
- modified = true;
- }
- }
-
- if (modified) {
- queue.IncrementVersion();
- idle_add(IDLE_PLAYLIST);
- }
-}
diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx
deleted file mode 100644
index 5bb0cdf64..000000000
--- a/src/PlaylistVector.cxx
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlaylistVector.hxx"
-#include "DatabaseLock.hxx"
-
-#include <algorithm>
-
-#include <assert.h>
-#include <string.h>
-
-PlaylistVector::iterator
-PlaylistVector::find(const char *name)
-{
- assert(holding_db_lock());
- assert(name != nullptr);
-
- 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
deleted file mode 100644
index 8ef8e44c7..000000000
--- a/src/PlaylistVector.hxx
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLAYLIST_VECTOR_HXX
-#define MPD_PLAYLIST_VECTOR_HXX
-
-#include "PlaylistInfo.hxx"
-#include "Compiler.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
deleted file mode 100644
index 92beefd0e..000000000
--- a/src/Queue.cxx
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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];
- ModifyAtPosition(position);
-}
-
-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(Queue *queue, unsigned start, unsigned end)
-{
- assert(queue != nullptr);
- 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
deleted file mode 100644
index 3c6001b4f..000000000
--- a/src/Queue.hxx
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_QUEUE_HXX
-#define MPD_QUEUE_HXX
-
-#include "Compiler.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;
-
- explicit Queue(unsigned max_length);
-
- /**
- * Deinitializes a queue object. It does not free the queue
- * pointer itself.
- */
- ~Queue();
-
- Queue(const Queue &) = delete;
- Queue &operator=(const Queue &) = 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". Call
- * IncrementVersion() after all modifications have been made.
- * number.
- */
- void ModifyAtPosition(unsigned position) {
- assert(position < length);
-
- items[position].version = version;
- }
-
- /**
- * Marks the specified song as "modified". Call
- * IncrementVersion() after all modifications have been made.
- * number.
- */
- void ModifyAtOrder(unsigned order);
-
- /**
- * 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/QueuePrint.cxx b/src/QueuePrint.cxx
deleted file mode 100644
index 89f3c0ad3..000000000
--- a/src/QueuePrint.cxx
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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 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 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 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 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 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 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
deleted file mode 100644
index bc91bb5de..000000000
--- a/src/QueuePrint.hxx
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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 Queue &queue,
- unsigned start, unsigned end);
-
-void
-queue_print_uris(Client &client, const Queue &queue,
- unsigned start, unsigned end);
-
-void
-queue_print_changes_info(Client &client, const Queue &queue,
- uint32_t version);
-
-void
-queue_print_changes_position(Client &client, const Queue &queue,
- uint32_t version);
-
-void
-queue_find(Client &client, const Queue &queue,
- const SongFilter &filter);
-
-#endif
diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx
deleted file mode 100644
index 2ab5f6280..000000000
--- a/src/QueueSave.cxx
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "QueueSave.hxx"
-#include "Queue.hxx"
-#include "PlaylistError.hxx"
-#include "Song.hxx"
-#include "SongSave.hxx"
-#include "DatabasePlugin.hxx"
-#include "DatabaseGlue.hxx"
-#include "TextFile.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
-#include "fs/Traits.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <stdlib.h>
-
-#define PRIO_LABEL "Prio: "
-
-static void
-queue_save_database_song(FILE *fp, int idx, const Song &song)
-{
- const auto uri = song.GetURI();
- fprintf(fp, "%i:%s\n", idx, uri.c_str());
-}
-
-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 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, nullptr, 10);
-
- line = file.ReadLine();
- if (line == nullptr)
- 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) && !PathTraits::IsAbsoluteUTF8(uri))
- return;
-
- Error error;
- song = song_load(file, nullptr, uri, error);
- if (song == nullptr) {
- LogError(error);
- return;
- }
- } else {
- char *endptr;
- long ret = strtol(line, &endptr, 10);
- if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
- LogError(playlist_domain,
- "Malformed playlist line in state file");
- return;
- }
-
- const char *uri = endptr + 1;
-
- if (uri_has_scheme(uri)) {
- song = Song::NewRemote(uri);
- } else {
- db = GetDatabase();
- if (db == nullptr)
- return;
-
- song = db->GetSong(uri, IgnoreError());
- if (song == nullptr)
- return;
- }
- }
-
- queue.Append(song, priority);
-
- if (db != nullptr)
- db->ReturnSong(song);
- else
- song->Free();
-}
diff --git a/src/QueueSave.hxx b/src/QueueSave.hxx
deleted file mode 100644
index 6c618c0dc..000000000
--- a/src/QueueSave.hxx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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 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
index 8f9b0d3f7..c3bbcac3c 100644
--- a/src/ReplayGainConfig.cxx
+++ b/src/ReplayGainConfig.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,14 +20,14 @@
#include "config.h"
#include "ReplayGainConfig.hxx"
#include "Idle.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "Playlist.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
#include "system/FatalError.hxx"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
+#include <math.h>
ReplayGainMode replay_gain_mode = REPLAY_GAIN_OFF;
diff --git a/src/ReplayGainConfig.hxx b/src/ReplayGainConfig.hxx
index 7777a859b..e498a56dd 100644
--- a/src/ReplayGainConfig.hxx
+++ b/src/ReplayGainConfig.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/ReplayGainInfo.cxx b/src/ReplayGainInfo.cxx
index cf6b0ba69..580773521 100644
--- a/src/ReplayGainInfo.cxx
+++ b/src/ReplayGainInfo.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/ReplayGainInfo.hxx b/src/ReplayGainInfo.hxx
index b8dfd16a1..37815c933 100644
--- a/src/ReplayGainInfo.hxx
+++ b/src/ReplayGainInfo.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/SignalHandlers.cxx b/src/SignalHandlers.cxx
deleted file mode 100644
index 542239083..000000000
--- a/src/SignalHandlers.cxx
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SignalHandlers.hxx"
-#include "event/SignalMonitor.hxx"
-
-#ifndef WIN32
-
-#include "Log.hxx"
-#include "LogInit.hxx"
-#include "event/Loop.hxx"
-#include "system/FatalError.hxx"
-#include "util/Domain.hxx"
-
-#include <signal.h>
-
-static constexpr Domain signal_handlers_domain("signal_handlers");
-
-static void
-HandleShutdownSignal()
-{
- SignalMonitorGetEventLoop().Break();
-}
-
-static void
-x_sigaction(int signum, const struct sigaction *act)
-{
- if (sigaction(signum, act, NULL) < 0)
- FatalSystemError("sigaction() failed");
-}
-
-static void
-handle_reload_event(void)
-{
- LogDebug(signal_handlers_domain, "got SIGHUP, reopening log files");
- cycle_log_files();
-}
-
-#endif
-
-void
-SignalHandlersInit(EventLoop &loop)
-{
- SignalMonitorInit(loop);
-
-#ifndef WIN32
- struct sigaction sa;
-
- sa.sa_flags = 0;
- sigemptyset(&sa.sa_mask);
- sa.sa_handler = SIG_IGN;
- x_sigaction(SIGPIPE, &sa);
-
- SignalMonitorRegister(SIGINT, HandleShutdownSignal);
- SignalMonitorRegister(SIGTERM, HandleShutdownSignal);
-
- SignalMonitorRegister(SIGHUP, handle_reload_event);
-#endif
-}
-
-void
-SignalHandlersFinish()
-{
- SignalMonitorFinish();
-}
diff --git a/src/SignalHandlers.hxx b/src/SignalHandlers.hxx
deleted file mode 100644
index 90dab6dec..000000000
--- a/src/SignalHandlers.hxx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SIGNAL_HANDLERS_HXX
-#define MPD_SIGNAL_HANDLERS_HXX
-
-class EventLoop;
-
-void
-SignalHandlersInit(EventLoop &loop);
-
-void
-SignalHandlersFinish();
-
-#endif
diff --git a/src/Song.cxx b/src/Song.cxx
deleted file mode 100644
index 6213d5e66..000000000
--- a/src/Song.cxx
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Song.hxx"
-#include "Directory.hxx"
-#include "tag/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()) {
- const auto new_uri = GetURI();
- song = NewDetached(new_uri.c_str());
- } else
- song = song_alloc(uri, nullptr);
-
- song->tag = tag != nullptr ? new Tag(*tag) : nullptr;
- song->mtime = mtime;
- song->start_ms = start_ms;
- song->end_ms = end_ms;
-
- return song;
-}
-
-void
-Song::Free()
-{
- delete tag;
- g_free(this);
-}
-
-void
-Song::ReplaceTag(Tag &&_tag)
-{
- if (tag == nullptr)
- tag = new Tag();
- *tag = std::move(_tag);
-}
-
-gcc_pure
-static inline bool
-directory_equals(const Directory &a, const Directory &b)
-{
- return strcmp(a.path, b.path) == 0;
-}
-
-gcc_pure
-static inline bool
-directory_is_same(const Directory *a, const Directory *b)
-{
- return a == b ||
- (a != nullptr && b != nullptr &&
- directory_equals(*a, *b));
-
-}
-
-bool
-SongEquals(const Song &a, const Song &b)
-{
- if (a.parent != nullptr && b.parent != nullptr &&
- !directory_equals(*a.parent, *b.parent) &&
- (a.parent == &detached_root || b.parent == &detached_root)) {
- /* must compare the full URI if one of the objects is
- "detached" */
- const auto au = a.GetURI();
- const auto bu = b.GetURI();
- return au == bu;
- }
-
- return directory_is_same(a.parent, b.parent) &&
- strcmp(a.uri, b.uri) == 0;
-}
-
-std::string
-Song::GetURI() const
-{
- assert(*uri);
-
- if (!IsInDatabase() || parent->IsRoot())
- return std::string(uri);
- else {
- const char *path = parent->GetPath();
-
- std::string result;
- result.reserve(strlen(path) + 1 + strlen(uri));
- result.assign(path);
- result.push_back('/');
- result.append(uri);
- return result;
- }
-}
-
-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
deleted file mode 100644
index b74690e77..000000000
--- a/src/Song.hxx
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SONG_HXX
-#define MPD_SONG_HXX
-
-#include "util/list.h"
-#include "Compiler.h"
-
-#include <string>
-
-#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);
-
- static Song *LoadFile(const char *path_utf8, Directory &parent) {
- return LoadFile(path_utf8, &parent);
- }
-
- /**
- * Replaces the URI of a song object. The given song object
- * is destroyed, and a newly allocated one is returned. It
- * does not update the reference within the parent directory;
- * the caller is responsible for doing that.
- */
- gcc_malloc
- Song *ReplaceURI(const char *uri);
-
- /**
- * Creates a "detached" song object.
- */
- gcc_malloc
- static Song *NewDetached(const char *uri);
-
- /**
- * Creates a duplicate of the song object. If the object is
- * in the database, it creates a "detached" copy of this song,
- * see Song::IsDetached().
- */
- gcc_malloc
- Song *DupDetached() const;
-
- void Free();
-
- bool IsInDatabase() const {
- return parent != nullptr;
- }
-
- bool IsFile() const {
- return IsInDatabase() || uri[0] == '/';
- }
-
- bool IsDetached() const {
- assert(IsInDatabase());
-
- return parent == &detached_root;
- }
-
- void ReplaceTag(Tag &&tag);
-
- bool UpdateFile();
- bool UpdateFileInArchive();
-
- /**
- * Returns the URI of the song in UTF-8 encoding, including its
- * location within the music directory.
- */
- gcc_pure
- std::string GetURI() const;
-
- gcc_pure
- double GetDuration() const;
-};
-
-/**
- * Returns true if both objects refer to the same physical song.
- */
-gcc_pure
-bool
-SongEquals(const Song &a, const Song &b);
-
-#endif
diff --git a/src/SongEnumerator.hxx b/src/SongEnumerator.hxx
deleted file mode 100644
index 0e268a31a..000000000
--- a/src/SongEnumerator.hxx
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SONG_ENUMERATOR_HXX
-#define MPD_SONG_ENUMERATOR_HXX
-
-struct Song;
-
-/**
- * An object which provides serial access to a number of #Song
- * objects. It is used to enumerate the contents of a playlist file.
- */
-class SongEnumerator {
-public:
- virtual ~SongEnumerator() {}
-
- /**
- * Obtain the next song. The caller is responsible for
- * freeing the returned #Song object. Returns nullptr if
- * there are no more songs.
- */
- virtual Song *NextSong() = 0;
-};
-
-#endif
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
index 01f9d8bb2..dc0a63df3 100644
--- a/src/SongFilter.cxx
+++ b/src/SongFilter.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,13 @@
#include "config.h"
#include "SongFilter.hxx"
-#include "Song.hxx"
+#include "db/LightSong.hxx"
+#include "DetachedSong.hxx"
#include "tag/Tag.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/ASCII.hxx"
#include "util/UriUtil.hxx"
-
-#include <glib.h>
+#include "lib/icu/Collate.hxx"
#include <assert.h>
#include <string.h>
@@ -47,17 +48,10 @@ locate_parse_type(const char *str)
if (strcmp(str, "base") == 0)
return LOCATE_TAG_BASE_TYPE;
- return tag_name_parse_i(str);
-}
+ if (strcmp(str, "modified-since") == 0)
+ return LOCATE_TAG_MODIFIED_SINCE;
-gcc_pure
-static std::string
-CaseFold(const char *p)
-{
- char *q = g_utf8_casefold(p, -1);
- std::string result(q);
- g_free(q);
- return result;
+ return tag_name_parse_i(str);
}
gcc_pure
@@ -65,7 +59,7 @@ static std::string
ImportString(const char *p, bool fold_case)
{
return fold_case
- ? CaseFold(p)
+ ? IcuCaseFold(p)
: std::string(p);
}
@@ -75,6 +69,11 @@ SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
{
}
+SongFilter::Item::Item(unsigned _tag, time_t _time)
+ :tag(_tag), time(_time)
+{
+}
+
bool
SongFilter::Item::StringMatch(const char *s) const
{
@@ -84,10 +83,8 @@ SongFilter::Item::StringMatch(const char *s) const
#endif
if (fold_case) {
- char *p = g_utf8_casefold(s, -1);
- const bool result = strstr(p, value.c_str()) != NULL;
- g_free(p);
- return result;
+ const std::string folded = IcuCaseFold(s);
+ return folded.find(value) != folded.npos;
} else {
return s == value;
}
@@ -106,10 +103,10 @@ SongFilter::Item::Match(const Tag &_tag) const
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
- for (unsigned i = 0; i < _tag.num_items; i++) {
- visited_types[_tag.items[i]->type] = true;
+ for (const auto &i : _tag) {
+ visited_types[i.type] = true;
- if (Match(*_tag.items[i]))
+ if (Match(i))
return true;
}
@@ -126,12 +123,10 @@ SongFilter::Item::Match(const Tag &_tag) const
if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
/* if we're looking for "album artist", but
only "artist" exists, use that */
- for (unsigned i = 0; i < _tag.num_items; i++) {
- const TagItem &item = *_tag.items[i];
+ for (const auto &item : _tag)
if (item.type == TAG_ARTIST &&
StringMatch(item.value))
return true;
- }
}
}
@@ -139,19 +134,37 @@ SongFilter::Item::Match(const Tag &_tag) const
}
bool
-SongFilter::Item::Match(const Song &song) const
+SongFilter::Item::Match(const DetachedSong &song) const
+{
+ if (tag == LOCATE_TAG_BASE_TYPE)
+ return uri_is_child_or_same(value.c_str(), song.GetURI());
+
+ if (tag == LOCATE_TAG_MODIFIED_SINCE)
+ return song.GetLastModified() >= time;
+
+ if (tag == LOCATE_TAG_FILE_TYPE)
+ return StringMatch(song.GetURI());
+
+ return Match(song.GetTag());
+}
+
+bool
+SongFilter::Item::Match(const LightSong &song) const
{
if (tag == LOCATE_TAG_BASE_TYPE) {
const auto uri = song.GetURI();
return uri_is_child_or_same(value.c_str(), uri.c_str());
}
+ if (tag == LOCATE_TAG_MODIFIED_SINCE)
+ return song.mtime >= time;
+
if (tag == LOCATE_TAG_FILE_TYPE) {
const auto uri = song.GetURI();
return StringMatch(uri.c_str());
}
- return song.tag != NULL && Match(*song.tag);
+ return Match(*song.tag);
}
SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case)
@@ -164,6 +177,58 @@ SongFilter::~SongFilter()
/* this destructor exists here just so it won't get inlined */
}
+#if !defined(__GLIBC__) && !defined(WIN32)
+
+/**
+ * Determine the time zone offset in a portable way.
+ */
+gcc_const
+static time_t
+GetTimeZoneOffset()
+{
+ time_t t = 1234567890;
+ struct tm tm;
+ tm.tm_isdst = 0;
+ gmtime_r(&t, &tm);
+ return t - mktime(&tm);
+}
+
+#endif
+
+gcc_pure
+static time_t
+ParseTimeStamp(const char *s)
+{
+ assert(s != nullptr);
+
+ char *endptr;
+ unsigned long long value = strtoull(s, &endptr, 10);
+ if (*endptr == 0 && endptr > s)
+ /* it's an integral UNIX time stamp */
+ return (time_t)value;
+
+#ifdef WIN32
+ /* TODO: emulate strptime()? */
+ return 0;
+#else
+ /* try ISO 8601 */
+
+ struct tm tm;
+ const char *end = strptime(s, "%FT%TZ", &tm);
+ if (end == nullptr || *end != 0)
+ return 0;
+
+#ifdef __GLIBC__
+ /* timegm() is a GNU extension */
+ return timegm(&tm);
+#else
+ tm.tm_isdst = 0;
+ return mktime(&tm) + GetTimeZoneOffset();
+#endif /* !__GLIBC__ */
+
+#endif /* !WIN32 */
+}
+
bool
SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
{
@@ -179,25 +244,44 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
fold_case = false;
}
+ if (tag == LOCATE_TAG_MODIFIED_SINCE) {
+ time_t t = ParseTimeStamp(value);
+ if (t == 0)
+ return false;
+
+ items.push_back(Item(tag, t));
+ return true;
+ }
+
items.push_back(Item(tag, value, fold_case));
return true;
}
bool
-SongFilter::Parse(unsigned argc, char *argv[], bool fold_case)
+SongFilter::Parse(ConstBuffer<const char *> args, bool fold_case)
{
- if (argc == 0 || argc % 2 != 0)
+ if (args.size == 0 || args.size % 2 != 0)
return false;
- for (unsigned i = 0; i < argc; i += 2)
- if (!Parse(argv[i], argv[i + 1], fold_case))
+ for (unsigned i = 0; i < args.size; i += 2)
+ if (!Parse(args[i], args[i + 1], fold_case))
+ return false;
+
+ return true;
+}
+
+bool
+SongFilter::Match(const DetachedSong &song) const
+{
+ for (const auto &i : items)
+ if (!i.Match(song))
return false;
return true;
}
bool
-SongFilter::Match(const Song &song) const
+SongFilter::Match(const LightSong &song) const
{
for (const auto &i : items)
if (!i.Match(song))
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
index 8c46ed5f3..f51bd85c6 100644
--- a/src/SongFilter.hxx
+++ b/src/SongFilter.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,18 +26,23 @@
#include <string>
#include <stdint.h>
+#include <time.h>
/**
* Limit the search to files within the given directory.
*/
#define LOCATE_TAG_BASE_TYPE (TAG_NUM_OF_ITEM_TYPES + 1)
+#define LOCATE_TAG_MODIFIED_SINCE (TAG_NUM_OF_ITEM_TYPES + 2)
#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10
#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20
+template<typename T> struct ConstBuffer;
struct Tag;
struct TagItem;
struct Song;
+struct LightSong;
+class DetachedSong;
class SongFilter {
public:
@@ -48,9 +53,15 @@ public:
std::string value;
+ /**
+ * For #LOCATE_TAG_MODIFIED_SINCE
+ */
+ time_t time;
+
public:
gcc_nonnull(3)
Item(unsigned tag, const char *value, bool fold_case=false);
+ Item(unsigned tag, time_t time);
Item(const Item &other) = delete;
Item(Item &&) = default;
@@ -79,7 +90,10 @@ public:
bool Match(const Tag &tag) const;
gcc_pure
- bool Match(const Song &song) const;
+ bool Match(const DetachedSong &song) const;
+
+ gcc_pure
+ bool Match(const LightSong &song) const;
};
private:
@@ -96,14 +110,16 @@ public:
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);
+ bool Parse(ConstBuffer<const char *> args, bool fold_case=false);
gcc_pure
bool Match(const Tag &tag) const;
gcc_pure
- bool Match(const Song &song) const;
+ bool Match(const DetachedSong &song) const;
+
+ gcc_pure
+ bool Match(const LightSong &song) const;
const std::list<Item> &GetItems() const {
return items;
diff --git a/src/SongLoader.cxx b/src/SongLoader.cxx
new file mode 100644
index 000000000..c766a16a9
--- /dev/null
+++ b/src/SongLoader.cxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SongLoader.hxx"
+#include "client/Client.hxx"
+#include "db/DatabaseSong.hxx"
+#include "storage/StorageInterface.hxx"
+#include "ls.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/Traits.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "DetachedSong.hxx"
+#include "PlaylistError.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#ifdef ENABLE_DATABASE
+
+SongLoader::SongLoader(const Client &_client)
+ :client(&_client), db(_client.GetDatabase(IgnoreError())),
+ storage(_client.GetStorage()) {}
+
+#endif
+
+DetachedSong *
+SongLoader::LoadFile(const char *path_utf8, Error &error) const
+{
+#ifdef ENABLE_DATABASE
+ if (storage != nullptr) {
+ const char *suffix = storage->MapToRelativeUTF8(path_utf8);
+ if (suffix != nullptr)
+ /* this path was relative to the music
+ directory - obtain it from the database */
+ return LoadSong(suffix, error);
+ }
+#endif
+
+ if (client != nullptr) {
+ const auto path_fs = AllocatedPath::FromUTF8(path_utf8, error);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ if (!client->AllowFile(path_fs, error))
+ return nullptr;
+ }
+
+ DetachedSong *song = new DetachedSong(path_utf8);
+ if (!song->Update()) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No such file");
+ delete song;
+ return nullptr;
+ }
+
+ return song;
+}
+
+DetachedSong *
+SongLoader::LoadSong(const char *uri_utf8, Error &error) const
+{
+ assert(uri_utf8 != nullptr);
+
+ if (memcmp(uri_utf8, "file:///", 8) == 0)
+ /* absolute path */
+ return LoadFile(uri_utf8 + 7, error);
+ else if (PathTraitsUTF8::IsAbsolute(uri_utf8))
+ /* absolute path */
+ return LoadFile(uri_utf8, error);
+ else if (uri_has_scheme(uri_utf8)) {
+ /* remove URI */
+ if (!uri_supported_scheme(uri_utf8)) {
+ error.Set(playlist_domain,
+ int(PlaylistResult::NO_SUCH_SONG),
+ "Unsupported URI scheme");
+ return nullptr;
+ }
+
+ return new DetachedSong(uri_utf8);
+ } else {
+ /* URI relative to the music directory */
+
+#ifdef ENABLE_DATABASE
+ if (db != nullptr)
+ return DatabaseDetachSong(*db, *storage,
+ uri_utf8, error);
+#endif
+
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No database");
+ return nullptr;
+ }
+}
diff --git a/src/SongLoader.hxx b/src/SongLoader.hxx
new file mode 100644
index 000000000..229703972
--- /dev/null
+++ b/src/SongLoader.hxx
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SONG_LOADER_HXX
+#define MPD_SONG_LOADER_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <cstddef>
+
+class Client;
+class Database;
+class Storage;
+class DetachedSong;
+class Error;
+
+/**
+ * A utility class that loads a #DetachedSong object by its URI. If
+ * the URI is an absolute local file, it applies security checks via
+ * Client::AllowFile(). If no #Client pointer was specified, then it
+ * is assumed that all local files are allowed.
+ */
+class SongLoader {
+ const Client *const client;
+
+#ifdef ENABLE_DATABASE
+ const Database *const db;
+ const Storage *const storage;
+#endif
+
+public:
+#ifdef ENABLE_DATABASE
+ explicit SongLoader(const Client &_client);
+ SongLoader(const Database *_db, const Storage *_storage)
+ :client(nullptr), db(_db), storage(_storage) {}
+ SongLoader(const Client &_client, const Database *_db,
+ const Storage *_storage)
+ :client(&_client), db(_db), storage(_storage) {}
+#else
+ explicit SongLoader(const Client &_client)
+ :client(&_client) {}
+ explicit SongLoader(std::nullptr_t, std::nullptr_t)
+ :client(nullptr) {}
+#endif
+
+#ifdef ENABLE_DATABASE
+ const Storage *GetStorage() const {
+ return storage;
+ }
+#endif
+
+ gcc_nonnull_all
+ DetachedSong *LoadSong(const char *uri_utf8, Error &error) const;
+
+private:
+ gcc_nonnull_all
+ DetachedSong *LoadFile(const char *path_utf8, Error &error) const;
+};
+
+#endif
diff --git a/src/SongPointer.hxx b/src/SongPointer.hxx
deleted file mode 100644
index ded3b3e1d..000000000
--- a/src/SongPointer.hxx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
index ea164d02b..05d462b6d 100644
--- a/src/SongPrint.cxx
+++ b/src/SongPrint.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,50 +19,108 @@
#include "config.h"
#include "SongPrint.hxx"
-#include "Song.hxx"
-#include "Directory.hxx"
+#include "db/LightSong.hxx"
+#include "storage/StorageInterface.hxx"
+#include "DetachedSong.hxx"
#include "TimePrint.hxx"
#include "TagPrint.hxx"
-#include "Mapper.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
+#include "fs/Traits.hxx"
#include "util/UriUtil.hxx"
-void
-song_print_uri(Client &client, const Song &song)
+#define SONG_FILE "file: "
+
+static void
+song_print_uri(Client &client, const char *uri, bool base)
{
- if (song.IsInDatabase() && !song.parent->IsRoot()) {
- client_printf(client, "%s%s/%s\n", SONG_FILE,
- song.parent->GetPath(), song.uri);
+ std::string allocated;
+
+ if (base) {
+ uri = PathTraitsUTF8::GetBase(uri);
} else {
- const char *uri = song.uri;
- const std::string allocated = uri_remove_auth(uri);
+#ifdef ENABLE_DATABASE
+ const Storage *storage = client.GetStorage();
+ if (storage != nullptr) {
+ const char *suffix = storage->MapToRelativeUTF8(uri);
+ if (suffix != nullptr)
+ uri = suffix;
+ }
+#endif
+
+ allocated = uri_remove_auth(uri);
if (!allocated.empty())
uri = allocated.c_str();
-
- client_printf(client, "%s%s\n", SONG_FILE,
- map_to_relative_path(uri));
}
+
+ client_printf(client, "%s%s\n", SONG_FILE, uri);
+}
+
+void
+song_print_uri(Client &client, const LightSong &song, bool base)
+{
+ if (!base && song.directory != nullptr) {
+ client_printf(client, "%s%s/%s\n", SONG_FILE,
+ song.directory, song.uri);
+ } else
+ song_print_uri(client, song.uri, base);
+}
+
+void
+song_print_uri(Client &client, const DetachedSong &song, bool base)
+{
+ song_print_uri(client, song.GetURI(), base);
}
void
-song_print_info(Client &client, const Song &song)
+song_print_info(Client &client, const LightSong &song, bool base)
{
- song_print_uri(client, song);
+ song_print_uri(client, song, base);
- if (song.end_ms > 0)
+ const unsigned start_ms = song.start_time.ToMS();
+ const unsigned end_ms = song.end_time.ToMS();
+
+ if (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)
+ start_ms / 1000,
+ start_ms % 1000,
+ end_ms / 1000,
+ end_ms % 1000);
+ else if (start_ms > 0)
client_printf(client, "Range: %u.%03u-\n",
- song.start_ms / 1000,
- song.start_ms % 1000);
+ start_ms / 1000,
+ start_ms % 1000);
if (song.mtime > 0)
time_print(client, "Last-Modified", song.mtime);
- if (song.tag != nullptr)
- tag_print(client, *song.tag);
+ tag_print(client, *song.tag);
+}
+
+void
+song_print_info(Client &client, const DetachedSong &song, bool base)
+{
+ song_print_uri(client, song, base);
+
+ const unsigned start_ms = song.GetStartTime().ToMS();
+ const unsigned end_ms = song.GetEndTime().ToMS();
+
+ if (end_ms > 0)
+ client_printf(client, "Range: %u.%03u-%u.%03u\n",
+ start_ms / 1000,
+ start_ms % 1000,
+ end_ms / 1000,
+ end_ms % 1000);
+ else if (start_ms > 0)
+ client_printf(client, "Range: %u.%03u-\n",
+ start_ms / 1000,
+ start_ms % 1000);
+
+ if (song.GetLastModified() > 0)
+ time_print(client, "Last-Modified", song.GetLastModified());
+
+ tag_print_values(client, song.GetTag());
+
+ const auto duration = song.GetDuration();
+ if (!duration.IsNegative())
+ client_printf(client, "Time: %u\n", duration.RoundS());
}
diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx
index f8df89d38..5e4c93a74 100644
--- a/src/SongPrint.hxx
+++ b/src/SongPrint.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,13 +20,20 @@
#ifndef MPD_SONG_PRINT_HXX
#define MPD_SONG_PRINT_HXX
-struct Song;
+struct LightSong;
+class DetachedSong;
class Client;
void
-song_print_info(Client &client, const Song &song);
+song_print_info(Client &client, const DetachedSong &song, bool base=false);
void
-song_print_uri(Client &client, const Song &song);
+song_print_info(Client &client, const LightSong &song, bool base=false);
+
+void
+song_print_uri(Client &client, const LightSong &song, bool base=false);
+
+void
+song_print_uri(Client &client, const DetachedSong &song, bool base=false);
#endif
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index 63e279a16..895e9805b 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,10 +19,11 @@
#include "config.h"
#include "SongSave.hxx"
-#include "Song.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "DetachedSong.hxx"
#include "TagSave.hxx"
-#include "Directory.hxx"
-#include "TextFile.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
@@ -37,41 +38,55 @@
static constexpr Domain song_save_domain("song_save");
+static void
+range_save(BufferedOutputStream &os, unsigned start_ms, unsigned end_ms)
+{
+ if (end_ms > 0)
+ os.Format("Range: %u-%u\n", start_ms, end_ms);
+ else if (start_ms > 0)
+ os.Format("Range: %u-\n", start_ms);
+}
+
+void
+song_save(BufferedOutputStream &os, const Song &song)
+{
+ os.Format(SONG_BEGIN "%s\n", song.uri);
+
+ range_save(os, song.start_time.ToMS(), song.end_time.ToMS());
+
+ tag_save(os, song.tag);
+
+ os.Format(SONG_MTIME ": %li\n", (long)song.mtime);
+ os.Format(SONG_END "\n");
+}
+
void
-song_save(FILE *fp, const Song &song)
+song_save(BufferedOutputStream &os, const DetachedSong &song)
{
- fprintf(fp, SONG_BEGIN "%s\n", song.uri);
+ os.Format(SONG_BEGIN "%s\n", song.GetURI());
- if (song.end_ms > 0)
- fprintf(fp, "Range: %u-%u\n", song.start_ms, song.end_ms);
- else if (song.start_ms > 0)
- fprintf(fp, "Range: %u-\n", song.start_ms);
+ range_save(os, song.GetStartTime().ToMS(), song.GetEndTime().ToMS());
- if (song.tag != nullptr)
- tag_save(fp, *song.tag);
+ tag_save(os, song.GetTag());
- fprintf(fp, SONG_MTIME ": %li\n", (long)song.mtime);
- fprintf(fp, SONG_END "\n");
+ os.Format(SONG_MTIME ": %li\n", (long)song.GetLastModified());
+ os.Format(SONG_END "\n");
}
-Song *
-song_load(TextFile &file, Directory *parent, const char *uri,
+DetachedSong *
+song_load(TextFile &file, const char *uri,
Error &error)
{
- Song *song = parent != nullptr
- ? Song::NewFile(uri, parent)
- : Song::NewRemote(uri);
- char *line, *colon;
- TagType type;
- const char *value;
+ DetachedSong *song = new DetachedSong(uri);
TagBuilder tag;
+ char *line;
while ((line = file.ReadLine()) != nullptr &&
strcmp(line, SONG_END) != 0) {
- colon = strchr(line, ':');
+ char *colon = strchr(line, ':');
if (colon == nullptr || colon == line) {
- song->Free();
+ delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
@@ -79,24 +94,29 @@ song_load(TextFile &file, Directory *parent, const char *uri,
}
*colon++ = 0;
- value = strchug_fast(colon);
+ const char *value = StripLeft(colon);
+ TagType type;
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) {
- tag.SetTime(atoi(value));
+ tag.SetDuration(SignedSongTime::FromS(atof(value)));
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {
- song->mtime = atoi(value);
+ song->SetLastModified(atoi(value));
} else if (strcmp(line, "Range") == 0) {
char *endptr;
- song->start_ms = strtoul(value, &endptr, 10);
- if (*endptr == '-')
- song->end_ms = strtoul(endptr + 1, nullptr, 10);
+ unsigned start_ms = strtoul(value, &endptr, 10);
+ unsigned end_ms = *endptr == '-'
+ ? strtoul(endptr + 1, nullptr, 10)
+ : 0;
+
+ song->SetStartTime(SongTime::FromMS(start_ms));
+ song->SetEndTime(SongTime::FromMS(end_ms));
} else {
- song->Free();
+ delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
@@ -104,8 +124,6 @@ song_load(TextFile &file, Directory *parent, const char *uri,
}
}
- if (tag.IsDefined())
- song->tag = tag.Commit();
-
+ song->SetTag(tag.Commit());
return song;
}
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
index 40fb4abf7..28c217249 100644
--- a/src/SongSave.hxx
+++ b/src/SongSave.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,17 +20,20 @@
#ifndef MPD_SONG_SAVE_HXX
#define MPD_SONG_SAVE_HXX
-#include <stdio.h>
-
#define SONG_BEGIN "song_begin: "
struct Song;
struct Directory;
+class DetachedSong;
+class BufferedOutputStream;
class TextFile;
class Error;
void
-song_save(FILE *fp, const Song &song);
+song_save(BufferedOutputStream &os, const Song &song);
+
+void
+song_save(BufferedOutputStream &os, const DetachedSong &song);
/**
* Loads a song from the input file. Reading stops after the
@@ -39,8 +42,8 @@ song_save(FILE *fp, const Song &song);
* @param error location to store the error occurring
* @return true on success, false on error
*/
-Song *
-song_load(TextFile &file, Directory *parent, const char *uri,
+DetachedSong *
+song_load(TextFile &file, const char *uri,
Error &error);
#endif
diff --git a/src/SongSort.cxx b/src/SongSort.cxx
deleted file mode 100644
index 4d422657a..000000000
--- a/src/SongSort.cxx
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SongSort.hxx"
-#include "Song.hxx"
-#include "util/list.h"
-#include "tag/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, TagType type)
-{
- return tag != nullptr
- ? tag->GetValue(type)
- : nullptr;
-}
-
-static int
-compare_utf8_string(const char *a, const char *b)
-{
- if (a == nullptr)
- return b == nullptr ? 0 : -1;
-
- if (b == nullptr)
- return 1;
-
- return g_utf8_collate(a, b);
-}
-
-/**
- * Compare two string tag values, ignoring case. Either one may be
- * nullptr.
- */
-static int
-compare_string_tag_item(const Tag *a, const Tag *b,
- TagType 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 nullptr.
- */
-static int
-compare_number_string(const char *a, const char *b)
-{
- long ai = a == nullptr ? 0 : strtol(a, nullptr, 10);
- long bi = b == nullptr ? 0 : strtol(b, nullptr, 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, TagType 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(gcc_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(nullptr, songs, song_cmp);
-}
diff --git a/src/SongSort.hxx b/src/SongSort.hxx
deleted file mode 100644
index b3b67b0c0..000000000
--- a/src/SongSort.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index a0c4d3585..000000000
--- a/src/SongSticker.cxx
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SongSticker.hxx"
-#include "StickerDatabase.hxx"
-#include "Song.hxx"
-#include "Directory.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-std::string
-sticker_song_get_value(const Song *song, const char *name)
-{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
- return sticker_load_value("song", uri.c_str(), name);
-}
-
-bool
-sticker_song_set_value(const Song *song,
- const char *name, const char *value)
-{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
- return sticker_store_value("song", uri.c_str(), name, value);
-}
-
-bool
-sticker_song_delete(const Song *song)
-{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
- return sticker_delete("song", uri.c_str());
-}
-
-bool
-sticker_song_delete_value(const Song *song, const char *name)
-{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
- return sticker_delete_value("song", uri.c_str(), name);
-}
-
-struct sticker *
-sticker_song_get(const Song *song)
-{
- assert(song != nullptr);
- assert(song->IsInDatabase());
-
- const auto uri = song->GetURI();
- return sticker_load("song", uri.c_str());
-}
-
-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 != nullptr)
- 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, "/", nullptr);
- else
- /* searching in root directory - no trailing slash */
- allocated = nullptr;
-
- data.base_uri_length = strlen(data.base_uri);
-
- bool success = sticker_find("song", data.base_uri, name,
- sticker_song_find_cb, &data);
- g_free(allocated);
-
- return success;
-}
diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx
deleted file mode 100644
index 0923f0c3a..000000000
--- a/src/SongSticker.hxx
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SONG_STICKER_HXX
-#define MPD_SONG_STICKER_HXX
-
-#include "Compiler.h"
-
-#include <string>
-
-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().
- */
-gcc_pure
-std::string
-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
index 1a873fedc..0245b9117 100644
--- a/src/SongUpdate.cxx
+++ b/src/SongUpdate.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,50 +18,44 @@
*/
#include "config.h" /* must be first for large file support */
-#include "Song.hxx"
+#include "DetachedSong.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "storage/StorageInterface.hxx"
+#include "storage/FileInfo.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
-#include "Directory.hxx"
-#include "Mapper.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
-#include "InputStream.hxx"
-#include "DecoderPlugin.hxx"
-#include "DecoderList.hxx"
+#include "decoder/DecoderList.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagId3.hxx"
#include "tag/ApeTag.hxx"
#include "TagFile.hxx"
-#include "thread/Cond.hxx"
+#include "TagStream.hxx"
#include <assert.h>
#include <string.h>
-#include <sys/types.h>
#include <sys/stat.h>
-#include <stdio.h>
+
+#ifdef ENABLE_DATABASE
Song *
-Song::LoadFile(const char *path_utf8, Directory *parent)
+Song::LoadFile(Storage &storage, const char *path_utf8, Directory &parent)
{
- Song *song;
- bool ret;
-
- assert((parent == nullptr) == PathTraits::IsAbsoluteUTF8(path_utf8));
assert(!uri_has_scheme(path_utf8));
assert(strchr(path_utf8, '\n') == nullptr);
- song = NewFile(path_utf8, parent);
+ Song *song = NewFile(path_utf8, parent);
//in archive ?
- if (parent != nullptr && parent->device == DEVICE_INARCHIVE) {
- ret = song->UpdateFileInArchive();
- } else {
- ret = song->UpdateFile();
- }
- if (!ret) {
+ bool success = parent.device == DEVICE_INARCHIVE
+ ? song->UpdateFileInArchive(storage)
+ : song->UpdateFile(storage);
+ if (!success) {
song->Free();
return nullptr;
}
@@ -69,6 +63,8 @@ Song::LoadFile(const char *path_utf8, Directory *parent)
return song;
}
+#endif
+
/**
* Attempts to load APE or ID3 tags from the specified file.
*/
@@ -80,59 +76,103 @@ tag_scan_fallback(Path path,
tag_id3_scan(path, handler, handler_ctx);
}
+#ifdef ENABLE_DATABASE
+
bool
-Song::UpdateFile()
+Song::UpdateFile(Storage &storage)
{
- assert(IsFile());
+ const auto &relative_uri = GetURI();
- const auto path_fs = map_song_fs(*this);
- if (path_fs.IsNull())
+ FileInfo info;
+ if (!storage.GetInfo(relative_uri.c_str(), true, info, IgnoreError()))
return false;
- struct stat st;
- if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode))
+ if (!info.IsRegular())
return false;
TagBuilder tag_builder;
- if (!tag_file_scan(path_fs,
- &full_tag_handler, &tag_builder))
- return false;
- if (tag_builder.IsEmpty())
- tag_scan_fallback(path_fs, &full_tag_handler,
- &tag_builder);
+ const auto path_fs = storage.MapFS(relative_uri.c_str());
+ if (path_fs.IsNull()) {
+ const auto absolute_uri =
+ storage.MapUTF8(relative_uri.c_str());
+ if (!tag_stream_scan(absolute_uri.c_str(),
+ full_tag_handler, &tag_builder))
+ return false;
+ } else {
+ if (!tag_file_scan(path_fs, full_tag_handler, &tag_builder))
+ return false;
- mtime = st.st_mtime;
+ if (tag_builder.IsEmpty())
+ tag_scan_fallback(path_fs, &full_tag_handler,
+ &tag_builder);
+ }
- delete tag;
- tag = tag_builder.Commit();
+ mtime = info.mtime;
+ tag_builder.Commit(tag);
return true;
}
bool
-Song::UpdateFileInArchive()
+Song::UpdateFileInArchive(const Storage &storage)
{
- const char *suffix;
- const struct DecoderPlugin *plugin;
-
- assert(IsFile());
-
/* check if there's a suffix and a plugin */
- suffix = uri_get_suffix(uri);
+ const char *suffix = uri_get_suffix(uri);
if (suffix == nullptr)
return false;
- plugin = decoder_plugin_from_suffix(suffix, nullptr);
- if (plugin == nullptr)
+ if (!decoder_plugins_supports_suffix(suffix))
return false;
- delete tag;
+ const auto path_fs = parent->IsRoot()
+ ? storage.MapFS(uri)
+ : storage.MapChildFS(parent->GetPath(), uri);
+ if (path_fs.IsNull())
+ return false;
- //accept every file that has music suffix
- //because we don't support tag reading through
- //input streams
- tag = new Tag();
+ TagBuilder tag_builder;
+ if (!tag_stream_scan(path_fs.c_str(), full_tag_handler, &tag_builder))
+ return false;
+ tag_builder.Commit(tag);
return true;
}
+
+#endif
+
+bool
+DetachedSong::Update()
+{
+ if (IsAbsoluteFile()) {
+ const AllocatedPath path_fs =
+ AllocatedPath::FromUTF8(GetRealURI());
+
+ struct stat st;
+ if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode))
+ return false;
+
+ TagBuilder tag_builder;
+ if (!tag_file_scan(path_fs, full_tag_handler, &tag_builder))
+ return false;
+
+ if (tag_builder.IsEmpty())
+ tag_scan_fallback(path_fs, &full_tag_handler,
+ &tag_builder);
+
+ mtime = st.st_mtime;
+ tag_builder.Commit(tag);
+ return true;
+ } else if (IsRemote()) {
+ TagBuilder tag_builder;
+ if (!tag_stream_scan(uri.c_str(), full_tag_handler,
+ &tag_builder))
+ return false;
+
+ mtime = 0;
+ tag_builder.Commit(tag);
+ return true;
+ } else
+ // TODO: implement
+ return false;
+}
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index 75cef2c99..7e9e35cc3 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,15 @@
#include "config.h"
#include "StateFile.hxx"
-#include "OutputState.hxx"
-#include "PlaylistState.hxx"
-#include "TextFile.hxx"
+#include "output/OutputState.hxx"
+#include "queue/PlaylistState.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/FileOutputStream.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
#include "Partition.hxx"
-#include "Volume.hxx"
-#include "event/Loop.hxx"
+#include "Instance.hxx"
+#include "mixer/Volume.hxx"
+#include "SongLoader.hxx"
#include "fs/FileSystem.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -33,10 +36,11 @@
static constexpr Domain state_file_domain("state_file");
-StateFile::StateFile(AllocatedPath &&_path,
+StateFile::StateFile(AllocatedPath &&_path, unsigned _interval,
Partition &_partition, EventLoop &_loop)
:TimeoutMonitor(_loop),
path(std::move(_path)), path_utf8(path.ToUTF8()),
+ interval(_interval),
partition(_partition),
prev_volume_version(0), prev_output_version(0),
prev_playlist_version(0)
@@ -61,25 +65,35 @@ StateFile::IsModified() const
partition.pc);
}
+inline void
+StateFile::Write(BufferedOutputStream &os)
+{
+ save_sw_volume_state(os);
+ audio_output_state_save(os, partition.outputs);
+ playlist_state_save(os, partition.playlist, partition.pc);
+}
+
+inline bool
+StateFile::Write(OutputStream &os, Error &error)
+{
+ BufferedOutputStream bos(os);
+ Write(bos);
+ return bos.Flush(error);
+}
+
void
StateFile::Write()
{
FormatDebug(state_file_domain,
"Saving state file %s", path_utf8.c_str());
- FILE *fp = FOpen(path, FOpenMode::WriteText);
- if (gcc_unlikely(!fp)) {
- FormatErrno(state_file_domain, "failed to create %s",
- path_utf8.c_str());
+ Error error;
+ FileOutputStream fos(path, error);
+ if (!fos.IsDefined() || !Write(fos, error) || !fos.Commit(error)) {
+ LogError(error);
return;
}
- save_sw_volume_state(fp);
- audio_output_state_save(fp);
- playlist_state_save(fp, partition.playlist, partition.pc);
-
- fclose(fp);
-
RememberVersions();
}
@@ -90,18 +104,26 @@ StateFile::Read()
FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str());
- TextFile file(path);
+ Error error;
+ TextFile file(path, error);
if (file.HasFailed()) {
- FormatErrno(state_file_domain, "failed to open %s",
- path_utf8.c_str());
+ LogError(error);
return;
}
+#ifdef ENABLE_DATABASE
+ const SongLoader song_loader(partition.instance.database,
+ partition.instance.storage);
+#else
+ const SongLoader song_loader(nullptr, nullptr);
+#endif
+
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,
+ while ((line = file.ReadLine()) != nullptr) {
+ success = read_sw_volume_state(line, partition.outputs) ||
+ audio_output_state_read(line, partition.outputs) ||
+ playlist_state_restore(line, file, song_loader,
+ partition.playlist,
partition.pc);
if (!success)
FormatError(state_file_domain,
@@ -116,7 +138,7 @@ void
StateFile::CheckModified()
{
if (!IsActive() && IsModified())
- ScheduleSeconds(2 * 60);
+ ScheduleSeconds(interval);
}
void
diff --git a/src/StateFile.hxx b/src/StateFile.hxx
index 4ec2c4be7..15ba13b97 100644
--- a/src/StateFile.hxx
+++ b/src/StateFile.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,10 +27,14 @@
#include <string>
struct Partition;
+class OutputStream;
+class BufferedOutputStream;
class StateFile final : private TimeoutMonitor {
- AllocatedPath path;
- std::string path_utf8;
+ const AllocatedPath path;
+ const std::string path_utf8;
+
+ const unsigned interval;
Partition &partition;
@@ -42,7 +46,10 @@ class StateFile final : private TimeoutMonitor {
prev_playlist_version;
public:
- StateFile(AllocatedPath &&path, Partition &partition, EventLoop &loop);
+ static constexpr unsigned DEFAULT_INTERVAL = 2 * 60;
+
+ StateFile(AllocatedPath &&path, unsigned interval,
+ Partition &partition, EventLoop &loop);
void Read();
void Write();
@@ -53,6 +60,9 @@ public:
void CheckModified();
private:
+ bool Write(OutputStream &os, Error &error);
+ void Write(BufferedOutputStream &os);
+
/**
* Save the current state versions for use with IsModified().
*/
diff --git a/src/Stats.cxx b/src/Stats.cxx
index f224bdf49..39d371ace 100644
--- a/src/Stats.cxx
+++ b/src/Stats.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,85 +20,123 @@
#include "config.h"
#include "Stats.hxx"
#include "PlayerControl.hxx"
-#include "Client.hxx"
-#include "DatabaseSelection.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabasePlugin.hxx"
-#include "DatabaseSimple.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "db/Selection.hxx"
+#include "db/Interface.hxx"
+#include "db/Stats.hxx"
#include "util/Error.hxx"
+#include "system/Clock.hxx"
#include "Log.hxx"
-#include <glib.h>
+#ifndef WIN32
+/**
+ * The monotonic time stamp when MPD was started. It is used to
+ * calculate the uptime.
+ */
+static unsigned start_time;
+#endif
+
+#ifdef ENABLE_DATABASE
-static GTimer *uptime;
static DatabaseStats stats;
+enum class StatsValidity : uint8_t {
+ INVALID, VALID, FAILED,
+};
+
+static StatsValidity stats_validity = StatsValidity::INVALID;
+
+#endif
+
void stats_global_init(void)
{
- uptime = g_timer_new();
+#ifndef WIN32
+ start_time = MonotonicClockS();
+#endif
}
-void stats_global_finish(void)
+#ifdef ENABLE_DATABASE
+
+void
+stats_invalidate()
{
- g_timer_destroy(uptime);
+ stats_validity = StatsValidity::INVALID;
}
-void stats_update(void)
+static bool
+stats_update(const Database &db)
{
- assert(GetDatabase() != nullptr);
+ switch (stats_validity) {
+ case StatsValidity::INVALID:
+ break;
- Error error;
+ case StatsValidity::VALID:
+ return true;
+
+ case StatsValidity::FAILED:
+ return false;
+ }
- DatabaseStats stats2;
+ Error error;
const DatabaseSelection selection("", true);
- if (GetDatabase()->GetStats(selection, stats2, error)) {
- stats = stats2;
+ if (db.GetStats(selection, stats, error)) {
+ stats_validity = StatsValidity::VALID;
+ return true;
} else {
LogError(error);
- stats.Clear();
+ stats_validity = StatsValidity::FAILED;
+ return false;
}
}
static void
-db_stats_print(Client &client)
+db_stats_print(Client &client, const Database &db)
{
- assert(GetDatabase() != nullptr);
+ if (!stats_update(db))
+ return;
- if (!db_is_simple())
- /* reload statistics if we're using the "proxy"
- database plugin */
- /* TODO: move this into the "proxy" database plugin as
- an "idle" handler */
- stats_update();
+ unsigned total_duration_s =
+ std::chrono::duration_cast<std::chrono::seconds>(stats.total_duration).count();
client_printf(client,
"artists: %u\n"
"albums: %u\n"
"songs: %u\n"
- "db_playtime: %lu\n",
+ "db_playtime: %u\n",
stats.artist_count,
stats.album_count,
stats.song_count,
- stats.total_duration);
+ total_duration_s);
- const time_t update_stamp = GetDatabase()->GetUpdateStamp();
+ const time_t update_stamp = db.GetUpdateStamp();
if (update_stamp > 0)
client_printf(client,
"db_update: %lu\n",
(unsigned long)update_stamp);
}
+#endif
+
void
stats_print(Client &client)
{
client_printf(client,
- "uptime: %lu\n"
+ "uptime: %u\n"
"playtime: %lu\n",
- (unsigned long)g_timer_elapsed(uptime, NULL),
+#ifdef WIN32
+ GetProcessUptimeS(),
+#else
+ MonotonicClockS() - start_time,
+#endif
(unsigned long)(client.player_control.GetTotalPlayTime() + 0.5));
- if (GetDatabase() != nullptr)
- db_stats_print(client);
+#ifdef ENABLE_DATABASE
+ const Database *db = client.partition.instance.database;
+ if (db != nullptr)
+ db_stats_print(client, *db);
+#endif
}
diff --git a/src/Stats.hxx b/src/Stats.hxx
index dd131ce19..0d36ec0b2 100644
--- a/src/Stats.hxx
+++ b/src/Stats.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,9 +24,8 @@ class Client;
void stats_global_init(void);
-void stats_global_finish(void);
-
-void stats_update(void);
+void
+stats_invalidate();
void
stats_print(Client &client);
diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx
deleted file mode 100644
index 869b91474..000000000
--- a/src/StickerDatabase.cxx
+++ /dev/null
@@ -1,604 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "StickerDatabase.hxx"
-#include "fs/Path.hxx"
-#include "Idle.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "Log.hxx"
-
-#include <string>
-#include <map>
-
-#include <sqlite3.h>
-#include <assert.h>
-
-#if SQLITE_VERSION_NUMBER < 3003009
-#define sqlite3_prepare_v2 sqlite3_prepare
-#endif
-
-struct sticker {
- std::map<std::string, std::string> table;
-};
-
-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[ARRAY_SIZE(sticker_sql)];
-
-static constexpr Domain sticker_domain("sticker");
-
-static void
-LogError(sqlite3 *db, const char *msg)
-{
- FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db));
-}
-
-static sqlite3_stmt *
-sticker_prepare(const char *sql, Error &error)
-{
- int ret;
- sqlite3_stmt *stmt;
-
- ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr);
- if (ret != SQLITE_OK) {
- error.Format(sticker_domain, ret,
- "sqlite3_prepare_v2() failed: %s",
- sqlite3_errmsg(sticker_db));
- return nullptr;
- }
-
- return stmt;
-}
-
-bool
-sticker_global_init(Path path, Error &error)
-{
- assert(!path.IsNull());
-
- int ret;
-
- /* open/create the sqlite database */
-
- ret = sqlite3_open(path.c_str(), &sticker_db);
- if (ret != SQLITE_OK) {
- const std::string utf8 = path.ToUTF8();
- error.Format(sticker_domain, ret,
- "Failed to open sqlite database '%s': %s",
- utf8.c_str(), sqlite3_errmsg(sticker_db));
- return false;
- }
-
- /* create the table and index */
-
- ret = sqlite3_exec(sticker_db, sticker_sql_create,
- nullptr, nullptr, nullptr);
- if (ret != SQLITE_OK) {
- error.Format(sticker_domain, 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 < ARRAY_SIZE(sticker_sql); ++i) {
- assert(sticker_sql[i] != nullptr);
-
- sticker_stmt[i] = sticker_prepare(sticker_sql[i], error);
- if (sticker_stmt[i] == nullptr)
- return false;
- }
-
- return true;
-}
-
-void
-sticker_global_finish(void)
-{
- if (sticker_db == nullptr)
- /* not configured */
- return;
-
- for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) {
- assert(sticker_stmt[i] != nullptr);
-
- sqlite3_finalize(sticker_stmt[i]);
- }
-
- sqlite3_close(sticker_db);
-}
-
-bool
-sticker_enabled(void)
-{
- return sticker_db != nullptr;
-}
-
-std::string
-sticker_load_value(const char *type, const char *uri, const char *name)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
- int ret;
-
- assert(sticker_enabled());
- assert(type != nullptr);
- assert(uri != nullptr);
- assert(name != nullptr);
-
- if (*name == 0)
- return std::string();
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return std::string();
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return std::string();
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return std::string();
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- std::string value;
- if (ret == SQLITE_ROW) {
- /* record found */
- value = (const char*)sqlite3_column_text(stmt, 0);
- } else if (ret == SQLITE_DONE) {
- /* no record found */
- } else {
- /* error */
- LogError(sticker_db, "sqlite3_step() failed");
- }
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- 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 != nullptr);
- assert(uri != nullptr);
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- switch (ret) {
- const char *name, *value;
-
- case SQLITE_ROW:
- name = (const char*)sqlite3_column_text(stmt, 0);
- value = (const char*)sqlite3_column_text(stmt, 1);
-
- table.insert(std::make_pair(name, value));
- break;
- case SQLITE_DONE:
- break;
- case SQLITE_BUSY:
- /* no op */
- break;
- default:
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
- } while (ret != SQLITE_DONE);
-
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-
- return true;
-}
-
-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 != nullptr);
- assert(uri != nullptr);
- assert(name != nullptr);
- assert(*name != 0);
- assert(value != nullptr);
-
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
-
- ret = sqlite3_changes(sticker_db);
-
- 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 != nullptr);
- assert(uri != nullptr);
- assert(name != nullptr);
- assert(*name != 0);
- assert(value != nullptr);
-
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- 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 != nullptr);
- assert(uri != nullptr);
- assert(name != nullptr);
- assert(value != nullptr);
-
- 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 != nullptr);
- assert(uri != nullptr);
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- 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 != nullptr);
- assert(uri != nullptr);
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- } while (ret == SQLITE_BUSY);
-
- if (ret != SQLITE_DONE) {
- LogError(sticker_db, "sqlite3_step() failed");
- return false;
- }
-
- ret = sqlite3_changes(sticker_db);
-
- 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 sticker &sticker,
- void (*func)(const char *name, const char *value,
- void *user_data),
- void *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 nullptr;
-
- if (s.table.empty())
- /* don't return empty sticker objects */
- return nullptr;
-
- return new sticker(std::move(s));
-}
-
-bool
-sticker_find(const char *type, const char *base_uri, const char *name,
- void (*func)(const char *uri, const char *value,
- void *user_data),
- void *user_data)
-{
- sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND];
- int ret;
-
- assert(type != nullptr);
- assert(name != nullptr);
- assert(func != nullptr);
- assert(sticker_enabled());
-
- sqlite3_reset(stmt);
-
- ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- if (base_uri == nullptr)
- base_uri = "";
-
- ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
- if (ret != SQLITE_OK) {
- LogError(sticker_db, "sqlite3_bind_text() failed");
- return false;
- }
-
- do {
- ret = sqlite3_step(stmt);
- 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:
- LogError(sticker_db, "sqlite3_step() failed");
- 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
deleted file mode 100644
index 42522b7b4..000000000
--- a/src/StickerDatabase.hxx
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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 "Compiler.h"
-
-#include <string>
-
-class Error;
-class Path;
-struct sticker;
-
-/**
- * Opens the sticker database.
- *
- * @return true on success, false on error
- */
-bool
-sticker_global_init(Path path, Error &error);
-
-/**
- * Close the sticker database.
- */
-void
-sticker_global_finish(void);
-
-/**
- * Returns true if the sticker database is configured and available.
- */
-gcc_const
-bool
-sticker_enabled(void);
-
-/**
- * Returns one value from an object's sticker record. Returns an
- * empty string if the value doesn't exist.
- */
-std::string
-sticker_load_value(const char *type, const char *uri, const char *name);
-
-/**
- * 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 nullptr if none was found
- */
-gcc_pure
-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 nullptr 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 nullptr 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
deleted file mode 100644
index 364d41356..000000000
--- a/src/StickerPrint.cxx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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 sticker &sticker)
-{
- sticker_foreach(sticker, print_sticker_cb, &client);
-}
diff --git a/src/StickerPrint.hxx b/src/StickerPrint.hxx
deleted file mode 100644
index be6708486..000000000
--- a/src/StickerPrint.hxx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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 sticker &sticker);
-
-#endif
diff --git a/src/TagFile.cxx b/src/TagFile.cxx
index 785a74987..7655b96ff 100644
--- a/src/TagFile.cxx
+++ b/src/TagFile.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,65 +22,77 @@
#include "fs/Path.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
-#include "DecoderList.hxx"
-#include "DecoderPlugin.hxx"
-#include "InputStream.hxx"
+#include "decoder/DecoderList.hxx"
+#include "decoder/DecoderPlugin.hxx"
+#include "input/InputStream.hxx"
+#include "input/LocalOpen.hxx"
#include "thread/Cond.hxx"
#include <assert.h>
-bool
-tag_file_scan(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- assert(!path_fs.IsNull());
- assert(handler != nullptr);
+class TagFileScan {
+ const Path path_fs;
+ const char *const suffix;
- /* check if there's a suffix and a plugin */
+ const tag_handler &handler;
+ void *handler_ctx;
- const char *suffix = uri_get_suffix(path_fs.c_str());
- if (suffix == nullptr)
- return false;
-
- const struct DecoderPlugin *plugin =
- decoder_plugin_from_suffix(suffix, nullptr);
- if (plugin == nullptr)
- return false;
-
- InputStream *is = nullptr;
Mutex mutex;
Cond cond;
+ InputStream *is;
+
+public:
+ TagFileScan(Path _path_fs, const char *_suffix,
+ const tag_handler &_handler, void *_handler_ctx)
+ :path_fs(_path_fs), suffix(_suffix),
+ handler(_handler), handler_ctx(_handler_ctx) ,
+ is(nullptr) {}
- do {
- /* load file tag */
- if (plugin->ScanFile(path_fs.c_str(),
- *handler, handler_ctx))
- break;
+ ~TagFileScan() {
+ delete is;
+ }
- /* fall back to stream tag */
- if (plugin->scan_stream != nullptr) {
- /* open the InputStream (if not already
- open) */
+ bool ScanFile(const DecoderPlugin &plugin) {
+ return plugin.ScanFile(path_fs, handler, handler_ctx);
+ }
+
+ bool ScanStream(const DecoderPlugin &plugin) {
+ if (plugin.scan_stream == nullptr)
+ return false;
+
+ /* open the InputStream (if not already open) */
+ if (is == nullptr) {
+ is = OpenLocalInputStream(path_fs,
+ mutex, cond,
+ IgnoreError());
if (is == nullptr)
- is = InputStream::Open(path_fs.c_str(),
- mutex, cond,
- IgnoreError());
+ return false;
+ } else
+ is->LockRewind(IgnoreError());
+
+ /* now try the stream_tag() method */
+ return plugin.ScanStream(*is, handler, handler_ctx);
+ }
- /* now try the stream_tag() method */
- if (is != nullptr) {
- if (plugin->ScanStream(*is,
- *handler, handler_ctx))
- break;
+ bool Scan(const DecoderPlugin &plugin) {
+ return plugin.SupportsSuffix(suffix) &&
+ (ScanFile(plugin) || ScanStream(plugin));
+ }
+};
- is->LockRewind(IgnoreError());
- }
- }
+bool
+tag_file_scan(Path path_fs, const tag_handler &handler, void *handler_ctx)
+{
+ assert(!path_fs.IsNull());
- plugin = decoder_plugin_from_suffix(suffix, plugin);
- } while (plugin != nullptr);
+ /* check if there's a suffix and a plugin */
- if (is != nullptr)
- is->Close();
+ const char *suffix = uri_get_suffix(path_fs.c_str());
+ if (suffix == nullptr)
+ return false;
- return plugin != nullptr;
+ TagFileScan tfs(path_fs, suffix, handler, handler_ctx);
+ return decoder_plugins_try([&](const DecoderPlugin &plugin){
+ return tfs.Scan(plugin);
+ });
}
diff --git a/src/TagFile.hxx b/src/TagFile.hxx
index 078abebd9..b11a8ac1c 100644
--- a/src/TagFile.hxx
+++ b/src/TagFile.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -33,7 +33,6 @@ struct tag_handler;
* found)
*/
bool
-tag_file_scan(Path path,
- const struct tag_handler *handler, void *handler_ctx);
+tag_file_scan(Path path, const tag_handler &handler, void *handler_ctx);
#endif
diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx
index 1191bd37c..4937fa622 100644
--- a/src/TagPrint.cxx
+++ b/src/TagPrint.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,8 +21,9 @@
#include "TagPrint.hxx"
#include "tag/Tag.hxx"
#include "tag/TagSettings.h"
-#include "Song.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
+
+#define SONG_TIME "Time: "
void tag_print_types(Client &client)
{
@@ -35,14 +36,24 @@ void tag_print_types(Client &client)
}
}
-void tag_print(Client &client, const Tag &tag)
+void
+tag_print(Client &client, TagType type, const char *value)
{
- if (tag.time >= 0)
- client_printf(client, SONG_TIME "%i\n", tag.time);
+ client_printf(client, "%s: %s\n", tag_item_names[type], value);
+}
- for (unsigned i = 0; i < tag.num_items; i++) {
+void
+tag_print_values(Client &client, const Tag &tag)
+{
+ for (const auto &i : tag)
client_printf(client, "%s: %s\n",
- tag_item_names[tag.items[i]->type],
- tag.items[i]->value);
- }
+ tag_item_names[i.type], i.value);
+}
+
+void tag_print(Client &client, const Tag &tag)
+{
+ if (!tag.duration.IsNegative())
+ client_printf(client, SONG_TIME "%i\n", tag.duration.RoundS());
+
+ tag_print_values(client, tag);
}
diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx
index ccc0c9aa4..6675bb7d8 100644
--- a/src/TagPrint.hxx
+++ b/src/TagPrint.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,12 +20,22 @@
#ifndef MPD_TAG_PRINT_HXX
#define MPD_TAG_PRINT_HXX
+#include <stdint.h>
+
+enum TagType : uint8_t;
+
struct Tag;
class Client;
void tag_print_types(Client &client);
void
+tag_print(Client &client, TagType type, const char *value);
+
+void
+tag_print_values(Client &client, const Tag &tag);
+
+void
tag_print(Client &client, const Tag &tag);
#endif
diff --git a/src/TagSave.cxx b/src/TagSave.cxx
index b20d986c2..107aca7db 100644
--- a/src/TagSave.cxx
+++ b/src/TagSave.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,19 +20,19 @@
#include "config.h"
#include "TagSave.hxx"
#include "tag/Tag.hxx"
-#include "Song.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+
+#define SONG_TIME "Time: "
void
-tag_save(FILE *file, const Tag &tag)
+tag_save(BufferedOutputStream &os, const Tag &tag)
{
- if (tag.time >= 0)
- fprintf(file, SONG_TIME "%i\n", tag.time);
+ if (!tag.duration.IsNegative())
+ os.Format(SONG_TIME "%f\n", tag.duration.ToDoubleS());
if (tag.has_playlist)
- fprintf(file, "Playlist: yes\n");
+ os.Format("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);
+ for (const auto &i : tag)
+ os.Format("%s: %s\n", tag_item_names[i.type], i.value);
}
diff --git a/src/TagSave.hxx b/src/TagSave.hxx
index 0b1359c89..fd4b91f98 100644
--- a/src/TagSave.hxx
+++ b/src/TagSave.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,10 @@
#ifndef MPD_TAG_SAVE_HXX
#define MPD_TAG_SAVE_HXX
-#include <stdio.h>
-
struct Tag;
+class BufferedOutputStream;
void
-tag_save(FILE *file, const Tag &tag);
+tag_save(BufferedOutputStream &os, const Tag &tag);
#endif
diff --git a/src/TagStream.cxx b/src/TagStream.cxx
new file mode 100644
index 000000000..6201028f6
--- /dev/null
+++ b/src/TagStream.cxx
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TagStream.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "decoder/DecoderList.hxx"
+#include "decoder/DecoderPlugin.hxx"
+#include "input/InputStream.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <assert.h>
+
+/**
+ * Does the #DecoderPlugin support either the suffix or the MIME type?
+ */
+gcc_pure
+static bool
+CheckDecoderPlugin(const DecoderPlugin &plugin,
+ const char *suffix, const char *mime)
+{
+ return (mime != nullptr && plugin.SupportsMimeType(mime)) ||
+ (suffix != nullptr && plugin.SupportsSuffix(suffix));
+}
+
+bool
+tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx)
+{
+ assert(is.IsReady());
+
+ UriSuffixBuffer suffix_buffer;
+ const char *const suffix = uri_get_suffix(is.GetURI(), suffix_buffer);
+ const char *const mime = is.GetMimeType();
+
+ if (suffix == nullptr && mime == nullptr)
+ return false;
+
+ return decoder_plugins_try([suffix, mime, &is,
+ &handler, ctx](const DecoderPlugin &plugin){
+ is.LockRewind(IgnoreError());
+
+ return CheckDecoderPlugin(plugin, suffix, mime) &&
+ plugin.ScanStream(is, handler, ctx);
+ });
+}
+
+bool
+tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx)
+{
+ Mutex mutex;
+ Cond cond;
+
+ InputStream *is = InputStream::OpenReady(uri, mutex, cond,
+ IgnoreError());
+ if (is == nullptr)
+ return false;
+
+ bool success = tag_stream_scan(*is, handler, ctx);
+ delete is;
+ return success;
+}
diff --git a/src/TagStream.hxx b/src/TagStream.hxx
new file mode 100644
index 000000000..71dd71ff7
--- /dev/null
+++ b/src/TagStream.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_STREAM_HXX
+#define MPD_TAG_STREAM_HXX
+
+#include "check.h"
+
+class InputStream;
+struct tag_handler;
+
+/**
+ * Scan the tags of an #InputStream. Invokes matching decoder
+ * plugins, but does not invoke the special "APE" and "ID3" scanners.
+ *
+ * @return true if the file was recognized (even if no metadata was
+ * found)
+ */
+bool
+tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx);
+
+bool
+tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx);
+
+#endif
diff --git a/src/TextFile.cxx b/src/TextFile.cxx
deleted file mode 100644
index 4a64ee963..000000000
--- a/src/TextFile.cxx
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "TextFile.hxx"
-#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-TextFile::TextFile(Path path_fs)
- :file(FOpen(path_fs, FOpenMode::ReadText)),
- buffer(g_string_sized_new(step)) {}
-
-TextFile::~TextFile()
-{
- if (file != nullptr)
- fclose(file);
-
- g_string_free(buffer, true);
-}
-
-char *
-TextFile::ReadLine()
-{
- gsize length = 0, i;
- char *p;
-
- assert(file != nullptr);
- assert(buffer != nullptr);
- assert(buffer->allocated_len >= step);
-
- while (buffer->len < max_length) {
- p = fgets(buffer->str + length,
- buffer->allocated_len - length, file);
- if (p == nullptr) {
- if (length == 0 || ferror(file))
- return nullptr;
- 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
deleted file mode 100644
index 9d8608711..000000000
--- a/src/TextFile.hxx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_TEXT_FILE_HXX
-#define MPD_TEXT_FILE_HXX
-
-#include "Compiler.h"
-
-#include <stdio.h>
-
-class Path;
-typedef struct _GString GString;
-
-class TextFile {
- static constexpr size_t max_length = 512 * 1024;
- static constexpr size_t step = 1024;
-
- FILE *const file;
-
- GString *const buffer;
-
-public:
- TextFile(Path path_fs);
-
- TextFile(const TextFile &other) = delete;
-
- ~TextFile();
-
- 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 nullptr on end-of-file or error
- */
- char *ReadLine();
-};
-
-#endif
diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx
deleted file mode 100644
index 36a726aa6..000000000
--- a/src/TextInputStream.cxx
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "TextInputStream.hxx"
-#include "InputStream.hxx"
-#include "util/CharUtil.hxx"
-#include "util/fifo_buffer.h"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-bool TextInputStream::ReadLine(std::string &line)
-{
- const char *src, *p;
-
- do {
- size_t nbytes;
- auto dest = buffer.Write();
- if (dest.size >= 2) {
- /* reserve one byte for the null terminator if
- the last line is not terminated by a
- newline character */
- --dest.size;
-
- Error error;
- nbytes = is.LockRead(dest.data, dest.size, error);
- if (nbytes > 0)
- buffer.Append(nbytes);
- else if (error.IsDefined()) {
- LogError(error);
- return false;
- }
- } else
- nbytes = 0;
-
- auto src_p = buffer.Read();
- if (src_p.IsEmpty())
- return false;
-
- src = src_p.data;
-
- p = reinterpret_cast<const char*>(memchr(src, '\n', src_p.size));
- if (p == nullptr && nbytes == 0) {
- /* end of file (or line too long): terminate
- the current line */
- dest = buffer.Write();
- assert(!dest.IsEmpty());
- dest.data[0] = '\n';
- buffer.Append(1);
- }
- } while (p == nullptr);
-
- size_t length = p - src + 1;
- while (p > src && IsWhitespaceOrNull(p[-1]))
- --p;
-
- line = std::string(src, p - src);
- buffer.Consume(length);
- return true;
-}
diff --git a/src/TextInputStream.hxx b/src/TextInputStream.hxx
deleted file mode 100644
index a6c15f670..000000000
--- a/src/TextInputStream.hxx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_TEXT_INPUT_STREAM_HXX
-#define MPD_TEXT_INPUT_STREAM_HXX
-
-#include "util/FifoBuffer.hxx"
-
-#include <string>
-
-struct InputStream;
-struct fifo_buffer;
-
-class TextInputStream {
- InputStream &is;
- FifoBuffer<char, 4096> 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(InputStream &_is)
- :is(_is) {}
-
- 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
index ee165d8e9..5526ec7d6 100644
--- a/src/TimePrint.cxx
+++ b/src/TimePrint.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
#include "config.h"
#include "TimePrint.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
void
time_print(Client &client, const char *name, time_t t)
diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx
index 55e235b66..afdb3c1c9 100644
--- a/src/TimePrint.hxx
+++ b/src/TimePrint.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/Timer.cxx b/src/Timer.cxx
deleted file mode 100644
index 661aa29ee..000000000
--- a/src/Timer.cxx
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Timer.hxx"
-#include "AudioFormat.hxx"
-#include "system/Clock.hxx"
-
-#include <glib.h>
-
-#include <limits>
-
-#include <assert.h>
-#include <limits.h>
-#include <stddef.h>
-
-Timer::Timer(const AudioFormat af)
- : time(0),
- started(false),
- rate(af.sample_rate * af.GetFrameSize())
-{
-}
-
-void Timer::Start()
-{
- time = MonotonicClockUS();
- 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 - MonotonicClockUS()) / 1000;
- if (delay < 0)
- return 0;
-
- if (delay > std::numeric_limits<int>::max())
- delay = std::numeric_limits<int>::max();
-
- return delay;
-}
-
-void Timer::Synchronize() const
-{
- int64_t sleep_duration;
-
- assert(started);
-
- sleep_duration = time - MonotonicClockUS();
- if (sleep_duration > 0)
- g_usleep(sleep_duration);
-}
diff --git a/src/Timer.hxx b/src/Timer.hxx
deleted file mode 100644
index c8b756e9c..000000000
--- a/src/Timer.hxx
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 3139a5926..000000000
--- a/src/UpdateArchive.cxx
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "UpdateArchive.hxx"
-#include "UpdateInternal.hxx"
-#include "UpdateDomain.hxx"
-#include "DatabaseLock.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
-#include "Mapper.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "ArchiveList.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "ArchiveVisitor.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <string>
-
-#include <string.h>
-
-static void
-update_archive_tree(Directory &directory, const char *name)
-{
- const char *tmp = strchr(name, '/');
- if (tmp) {
- const std::string child_name(name, tmp);
- //add dir is not there already
- db_lock();
- Directory *subdir =
- directory.MakeChild(child_name.c_str());
- subdir->device = DEVICE_INARCHIVE;
- db_unlock();
-
- //create directories first
- update_archive_tree(*subdir, tmp+1);
- } else {
- if (strlen(name) == 0) {
- LogWarning(update_domain,
- "archive returned directory only");
- return;
- }
-
- //add file
- db_lock();
- Song *song = directory.FindSong(name);
- db_unlock();
- if (song == nullptr) {
- song = Song::LoadFile(name, directory);
- if (song != nullptr) {
- db_lock();
- directory.AddSong(song);
- db_unlock();
-
- modified = true;
- FormatDefault(update_domain, "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 != nullptr && 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 auto path_fs = map_directory_child_fs(parent, name);
-
- /* open archive */
- Error error;
- ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), error);
- if (file == nullptr) {
- LogError(error);
- return;
- }
-
- FormatDebug(update_domain, "archive %s opened", path_fs.c_str());
-
- if (directory == nullptr) {
- FormatDebug(update_domain,
- "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 {
- FormatDebug(update_domain,
- "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 == nullptr)
- 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
deleted file mode 100644
index 8f52ca0b6..000000000
--- a/src/UpdateArchive.hxx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_UPDATE_ARCHIVE_HXX
-#define MPD_UPDATE_ARCHIVE_HXX
-
-#include "check.h"
-#include "Compiler.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
deleted file mode 100644
index 80f059734..000000000
--- a/src/UpdateContainer.cxx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "UpdateContainer.hxx"
-#include "UpdateInternal.hxx"
-#include "UpdateDatabase.hxx"
-#include "UpdateDomain.hxx"
-#include "DatabaseLock.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
-#include "DecoderPlugin.hxx"
-#include "Mapper.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/TagBuilder.hxx"
-#include "Log.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 nullptr 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 != nullptr) {
- if (directory->mtime == st->st_mtime && !walk_discard) {
- /* not modified */
- return nullptr;
- }
-
- 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 DecoderPlugin &plugin)
-{
- if (plugin.container_scan == nullptr)
- return false;
-
- db_lock();
- Directory *contdir = make_directory_if_modified(directory, name, st);
- if (contdir == nullptr) {
- /* not modified */
- db_unlock();
- return true;
- }
-
- contdir->device = DEVICE_CONTAINER;
- db_unlock();
-
- const auto pathname = map_directory_child_fs(directory, name);
-
- char *vtrack;
- unsigned int tnum = 0;
- TagBuilder tag_builder;
- while ((vtrack = plugin.container_scan(pathname.c_str(), ++tnum)) != nullptr) {
- Song *song = Song::NewFile(vtrack, contdir);
-
- // shouldn't be necessary but it's there..
- song->mtime = st->st_mtime;
-
- const auto child_path_fs =
- map_directory_child_fs(*contdir, vtrack);
-
- plugin.ScanFile(child_path_fs.c_str(),
- add_tag_handler, &tag_builder);
-
- if (tag_builder.IsDefined())
- song->tag = tag_builder.Commit();
- else
- tag_builder.Clear();
-
- db_lock();
- contdir->AddSong(song);
- db_unlock();
-
- modified = true;
-
- FormatDefault(update_domain, "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
deleted file mode 100644
index 3b54fb39f..000000000
--- a/src/UpdateContainer.hxx
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_UPDATE_CONTAINER_HXX
-#define MPD_UPDATE_CONTAINER_HXX
-
-#include "check.h"
-
-#include <sys/stat.h>
-
-struct Directory;
-struct DecoderPlugin;
-
-bool
-update_container_file(Directory &directory,
- const char *name,
- const struct stat *st,
- const DecoderPlugin &plugin);
-
-#endif
diff --git a/src/UpdateDatabase.cxx b/src/UpdateDatabase.cxx
deleted file mode 100644
index 5dd2bb496..000000000
--- a/src/UpdateDatabase.cxx
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* 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 <assert.h>
-#include <stddef.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 != nullptr);
-
- 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 != nullptr) {
- delete_directory(directory);
- modified = true;
- }
-
- Song *song = parent.FindSong(name);
- if (song != nullptr) {
- delete_song(parent, song);
- modified = true;
- }
-
- parent.playlists.erase(name);
-
- db_unlock();
-
- return modified;
-}
diff --git a/src/UpdateDatabase.hxx b/src/UpdateDatabase.hxx
deleted file mode 100644
index 0462dc778..000000000
--- a/src/UpdateDatabase.hxx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/UpdateDomain.cxx b/src/UpdateDomain.cxx
deleted file mode 100644
index a2bbd5b70..000000000
--- a/src/UpdateDomain.cxx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "UpdateDomain.hxx"
-#include "util/Domain.hxx"
-
-const Domain update_domain("update");
diff --git a/src/UpdateDomain.hxx b/src/UpdateDomain.hxx
deleted file mode 100644
index e7528a57e..000000000
--- a/src/UpdateDomain.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_UPDATE_DOMAIN_HXX
-#define MPD_UPDATE_DOMAIN_HXX
-
-extern const class Domain update_domain;
-
-#endif
diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx
deleted file mode 100644
index 12ea126a9..000000000
--- a/src/UpdateGlue.cxx
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "UpdateGlue.hxx"
-#include "UpdateQueue.hxx"
-#include "UpdateWalk.hxx"
-#include "UpdateRemove.hxx"
-#include "UpdateDomain.hxx"
-#include "Mapper.hxx"
-#include "DatabaseSimple.hxx"
-#include "Idle.hxx"
-#include "GlobalEvents.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-#include "Stats.hxx"
-#include "Main.hxx"
-#include "Instance.hxx"
-#include "system/FatalError.hxx"
-#include "thread/Id.hxx"
-#include "thread/Thread.hxx"
-
-#include <assert.h>
-
-static enum update_progress {
- UPDATE_PROGRESS_IDLE = 0,
- UPDATE_PROGRESS_RUNNING = 1,
- UPDATE_PROGRESS_DONE = 2
-} progress;
-
-static bool modified;
-
-static Thread update_thread;
-
-static const unsigned update_task_id_max = 1 << 15;
-
-static unsigned update_task_id;
-
-static UpdateQueueItem next;
-
-unsigned
-isUpdatingDB(void)
-{
- return next.id;
-}
-
-static void
-update_task(gcc_unused void *ctx)
-{
- if (!next.path_utf8.empty())
- FormatDebug(update_domain, "starting: %s",
- next.path_utf8.c_str());
- else
- LogDebug(update_domain, "starting");
-
- modified = update_walk(next.path_utf8.c_str(), next.discard);
-
- if (modified || !db_exists()) {
- Error error;
- if (!db_save(error))
- LogError(error, "Failed to save database");
- }
-
- if (!next.path_utf8.empty())
- FormatDebug(update_domain, "finished: %s",
- next.path_utf8.c_str());
- else
- LogDebug(update_domain, "finished");
-
- progress = UPDATE_PROGRESS_DONE;
- GlobalEvents::Emit(GlobalEvents::UPDATE);
-}
-
-static void
-spawn_update_task(UpdateQueueItem &&i)
-{
- assert(main_thread.IsInside());
-
- progress = UPDATE_PROGRESS_RUNNING;
- modified = false;
-
- next = std::move(i);
-
- Error error;
- if (!update_thread.Start(update_task, nullptr, error))
- FatalError(error);
-
- FormatDebug(update_domain,
- "spawned thread for update job id %i", next.id);
-}
-
-static unsigned
-generate_update_id()
-{
- unsigned id = update_task_id + 1;
- if (id > update_task_id_max)
- id = 1;
- return id;
-}
-
-unsigned
-update_enqueue(const char *path, bool discard)
-{
- assert(main_thread.IsInside());
-
- if (!db_is_simple() || !mapper_has_music_directory())
- return 0;
-
- if (progress != UPDATE_PROGRESS_IDLE) {
- const unsigned id = generate_update_id();
- if (!update_queue_push(path, discard, id))
- return 0;
-
- update_task_id = id;
- return id;
- }
-
- const unsigned id = update_task_id = generate_update_id();
- spawn_update_task(UpdateQueueItem(path, discard, id));
-
- idle_add(IDLE_UPDATE);
-
- return id;
-}
-
-/**
- * Called in the main thread after the database update is finished.
- */
-static void update_finished_event(void)
-{
- assert(progress == UPDATE_PROGRESS_DONE);
- assert(next.IsDefined());
-
- update_thread.Join();
- next = UpdateQueueItem();
-
- idle_add(IDLE_UPDATE);
-
- if (modified)
- /* send "idle" events */
- instance->DatabaseModified();
-
- auto i = update_queue_shift();
- if (i.IsDefined()) {
- /* schedule the next path */
- spawn_update_task(std::move(i));
- } 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
deleted file mode 100644
index c9fc0f9a1..000000000
--- a/src/UpdateGlue.hxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_UPDATE_GLUE_HXX
-#define MPD_UPDATE_GLUE_HXX
-
-#include "Compiler.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 an empty string,
- * the whole music directory is updated
- * @return the job id, or 0 on error
- */
-gcc_nonnull_all
-unsigned
-update_enqueue(const char *path, bool discard);
-
-#endif
diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx
deleted file mode 100644
index e70e07db0..000000000
--- a/src/UpdateIO.cxx
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "UpdateIO.hxx"
-#include "src/UpdateDomain.hxx"
-#include "Directory.hxx"
-#include "Mapper.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "Log.hxx"
-
-#include <errno.h>
-#include <unistd.h>
-
-int
-stat_directory(const Directory &directory, struct stat *st)
-{
- const auto 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();
- FormatErrno(update_domain, error,
- "Failed to stat %s", path_utf8.c_str());
- return -1;
- }
-
- return 0;
-}
-
-int
-stat_directory_child(const Directory &parent, const char *name,
- struct stat *st)
-{
- const auto 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();
- FormatErrno(update_domain, error,
- "Failed to stat %s", path_utf8.c_str());
- return -1;
- }
-
- return 0;
-}
-
-bool
-directory_exists(const Directory &directory)
-{
- const auto 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 auto 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 auto 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
deleted file mode 100644
index 9d6c197f2..000000000
--- a/src/UpdateIO.hxx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index de0850ece..000000000
--- a/src/UpdateInternal.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 2a30e5d5f..000000000
--- a/src/UpdateQueue.cxx
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "UpdateQueue.hxx"
-
-#include <queue>
-#include <list>
-
-static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32;
-
-static std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue;
-
-bool
-update_queue_push(const char *path, bool discard, unsigned id)
-{
- if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE)
- return false;
-
- update_queue.emplace(path, discard, id);
- return true;
-}
-
-UpdateQueueItem
-update_queue_shift()
-{
- if (update_queue.empty())
- return UpdateQueueItem();
-
- auto i = std::move(update_queue.front());
- update_queue.pop();
- return i;
-}
diff --git a/src/UpdateQueue.hxx b/src/UpdateQueue.hxx
deleted file mode 100644
index 2769cc589..000000000
--- a/src/UpdateQueue.hxx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_UPDATE_QUEUE_HXX
-#define MPD_UPDATE_QUEUE_HXX
-
-#include "check.h"
-
-#include <string>
-
-struct UpdateQueueItem {
- std::string path_utf8;
- unsigned id;
- bool discard;
-
- UpdateQueueItem():id(0) {}
- UpdateQueueItem(const char *_path, bool _discard,
- unsigned _id)
- :path_utf8(_path), id(_id), discard(_discard) {}
-
- bool IsDefined() const {
- return id != 0;
- }
-};
-
-bool
-update_queue_push(const char *path, bool discard, unsigned id);
-
-UpdateQueueItem
-update_queue_shift();
-
-#endif
diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx
deleted file mode 100644
index f4043b2f3..000000000
--- a/src/UpdateRemove.cxx
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "UpdateRemove.hxx"
-#include "UpdateDomain.hxx"
-#include "Playlist.hxx"
-#include "GlobalEvents.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "Song.hxx"
-#include "Main.hxx"
-#include "Instance.hxx"
-#include "Log.hxx"
-
-#ifdef ENABLE_SQLITE
-#include "StickerDatabase.hxx"
-#include "SongSticker.hxx"
-#endif
-
-#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)
-{
- assert(removed_song != nullptr);
-
- {
- const auto uri = removed_song->GetURI();
- FormatDefault(update_domain, "removing %s", uri.c_str());
- }
-
-#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 = nullptr;
- 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 == nullptr);
-
- removed_song = song;
-
- GlobalEvents::Emit(GlobalEvents::DELETE);
-
- remove_mutex.lock();
-
- while (removed_song != nullptr)
- remove_cond.wait(remove_mutex);
-
- remove_mutex.unlock();
-}
diff --git a/src/UpdateRemove.hxx b/src/UpdateRemove.hxx
deleted file mode 100644
index bef27d766..000000000
--- a/src/UpdateRemove.hxx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index bfab5c4a0..000000000
--- a/src/UpdateSong.cxx
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "UpdateSong.hxx"
-#include "UpdateInternal.hxx"
-#include "UpdateIO.hxx"
-#include "UpdateDatabase.hxx"
-#include "UpdateContainer.hxx"
-#include "UpdateDomain.hxx"
-#include "DatabaseLock.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
-#include "DecoderPlugin.hxx"
-#include "DecoderList.hxx"
-#include "Log.hxx"
-
-#include <unistd.h>
-
-static void
-update_song_file2(Directory &directory,
- const char *name, const struct stat *st,
- const DecoderPlugin &plugin)
-{
- db_lock();
- Song *song = directory.FindSong(name);
- db_unlock();
-
- if (!directory_child_access(directory, name, R_OK)) {
- FormatError(update_domain,
- "no read permissions on %s/%s",
- directory.GetPath(), name);
- if (song != nullptr) {
- db_lock();
- delete_song(directory, song);
- db_unlock();
- }
-
- return;
- }
-
- if (!(song != nullptr && st->st_mtime == song->mtime &&
- !walk_discard) &&
- update_container_file(directory, name, st, plugin)) {
- if (song != nullptr) {
- db_lock();
- delete_song(directory, song);
- db_unlock();
- }
-
- return;
- }
-
- if (song == nullptr) {
- FormatDebug(update_domain, "reading %s/%s",
- directory.GetPath(), name);
- song = Song::LoadFile(name, &directory);
- if (song == nullptr) {
- FormatDebug(update_domain,
- "ignoring unrecognized file %s/%s",
- directory.GetPath(), name);
- return;
- }
-
- db_lock();
- directory.AddSong(song);
- db_unlock();
-
- modified = true;
- FormatDefault(update_domain, "added %s/%s",
- directory.GetPath(), name);
- } else if (st->st_mtime != song->mtime || walk_discard) {
- FormatDefault(update_domain, "updating %s/%s",
- directory.GetPath(), name);
- if (!song->UpdateFile()) {
- FormatDebug(update_domain,
- "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 DecoderPlugin *plugin =
- decoder_plugin_from_suffix(suffix, nullptr);
- if (plugin == nullptr)
- return false;
-
- update_song_file2(directory, name, st, *plugin);
- return true;
-}
diff --git a/src/UpdateSong.hxx b/src/UpdateSong.hxx
deleted file mode 100644
index 00a7bfd27..000000000
--- a/src/UpdateSong.hxx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index d4586456b..000000000
--- a/src/UpdateWalk.cxx
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "UpdateWalk.hxx"
-#include "UpdateIO.hxx"
-#include "UpdateDatabase.hxx"
-#include "UpdateSong.hxx"
-#include "UpdateArchive.hxx"
-#include "UpdateDomain.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 "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/Traits.hxx"
-#include "fs/FileSystem.hxx"
-#include "fs/DirectoryReader.hxx"
-#include "util/UriUtil.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-bool walk_discard;
-bool modified;
-
-#ifndef WIN32
-
-static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true;
-static constexpr bool 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 auto name_fs = AllocatedPath::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 auto name_fs = AllocatedPath::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 auto 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 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 WIN32
- while (parent) {
- if (!parent->have_stat && update_directory_stat(*parent) < 0)
- return -1;
-
- if (parent->inode == inode && parent->device == device) {
- LogDebug(update_domain, "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 == nullptr)
- 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, '/') == nullptr);
-
- 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 {
- FormatDebug(update_domain,
- "%s is not a directory, archive or music", name);
- }
-}
-
-/* we don't look at "." / ".." nor files with newlines in their name */
-gcc_pure
-static bool skip_path(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') != nullptr;
-}
-
-gcc_pure
-static bool
-skip_symlink(const Directory *directory, const char *utf8_name)
-{
-#ifndef WIN32
- const auto path_fs = map_directory_child_fs(*directory, utf8_name);
- if (path_fs.IsNull())
- return true;
-
- const auto 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 (PathTraits::IsAbsoluteFS(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] == '.' && PathTraits::IsSeparatorFS(p[2])) {
- /* "../" moves to parent directory */
- directory = directory->parent;
- if (directory == nullptr) {
- /* we have moved outside the music
- directory - skip this symlink
- if such symlinks are not allowed */
- return !follow_outside_symlinks;
- }
- p += 3;
- } else if (PathTraits::IsSeparatorFS(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 auto 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();
- FormatErrno(update_domain, error,
- "Failed to open directory %s",
- path_utf8.c_str());
- return false;
- }
-
- ExcludeList exclude_list;
- exclude_list.LoadFile(AllocatedPath::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 auto 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 != nullptr)
- 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 nullptr;
-
- if (skip_symlink(&parent, name_utf8))
- return nullptr;
-
- /* 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, '/')) != nullptr) {
- *slash = 0;
-
- if (*name_utf8 == 0)
- continue;
-
- directory = directory_make_child_checked(*directory,
- name_utf8);
- if (directory == nullptr)
- 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 == nullptr)
- return;
-
- const char *name = PathTraits::GetBaseUTF8(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);
-}
-
-bool
-update_walk(const char *path, bool discard)
-{
- walk_discard = discard;
- modified = false;
-
- if (path != nullptr && !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
deleted file mode 100644
index 62c0d0a8e..000000000
--- a/src/UpdateWalk.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 6c5f8dc4d..000000000
--- a/src/Volume.cxx
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "Volume.hxx"
-#include "MixerAll.hxx"
-#include "Idle.hxx"
-#include "GlobalEvents.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-#define SW_VOLUME_STATE "sw_volume: "
-
-static constexpr Domain volume_domain("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 != nullptr);
-
- if (last_hardware_volume >= 0 &&
- g_timer_elapsed(hardware_volume_timer, nullptr) < 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 = nullptr;
- 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
- FormatWarning(volume_domain,
- "Can't parse software volume: %s", 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
deleted file mode 100644
index 6b937aca3..000000000
--- a/src/Volume.hxx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_VOLUME_HXX
-#define MPD_VOLUME_HXX
-
-#include "Compiler.h"
-
-#include <stdio.h>
-
-void volume_init(void);
-
-void volume_finish(void);
-
-gcc_pure
-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.
- */
-gcc_pure
-unsigned
-sw_volume_state_get_hash(void);
-
-#endif
diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx
deleted file mode 100644
index 083647b42..000000000
--- a/src/ZeroconfAvahi.cxx
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ZeroconfAvahi.hxx"
-#include "AvahiPoll.hxx"
-#include "ZeroconfInternal.hxx"
-#include "Listen.hxx"
-#include "system/FatalError.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <avahi-client/client.h>
-#include <avahi-client/publish.h>
-
-#include <avahi-common/watch.h>
-#include <avahi-common/alternative.h>
-#include <avahi-common/domain.h>
-#include <avahi-common/malloc.h>
-#include <avahi-common/error.h>
-
-#include <stddef.h>
-
-static constexpr Domain avahi_domain("avahi");
-
-static char *avahiName;
-static bool avahi_running;
-static MyAvahiPoll *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,
- gcc_unused void *userdata)
-{
- char *n;
- assert(g);
-
- FormatDebug(avahi_domain,
- "Service group changed to state %d", state);
-
- switch (state) {
- case AVAHI_ENTRY_GROUP_ESTABLISHED:
- /* The entry group has been established successfully */
- FormatDefault(avahi_domain,
- "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;
-
- FormatDefault(avahi_domain,
- "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:
- FormatError(avahi_domain,
- "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 */
- avahi_running = false;
- break;
-
- case AVAHI_ENTRY_GROUP_UNCOMMITED:
- LogDebug(avahi_domain, "Service group is UNCOMMITED");
- break;
- case AVAHI_ENTRY_GROUP_REGISTERING:
- LogDebug(avahi_domain, "Service group is REGISTERING");
- }
-}
-
-/* Registers a new service with avahi */
-static void avahiRegisterService(AvahiClient * c)
-{
- FormatDebug(avahi_domain, "Registering service %s/%s",
- SERVICE_TYPE, avahiName);
-
- int ret;
- assert(c);
-
- /* 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, nullptr);
- if (!avahiGroup) {
- FormatError(avahi_domain,
- "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, nullptr,
- nullptr, listen_port, nullptr);
- if (ret < 0) {
- FormatError(avahi_domain, "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) {
- FormatError(avahi_domain, "Failed to commit service group: %s",
- avahi_strerror(ret));
- goto fail;
- }
- return;
-
-fail:
- avahi_running = false;
-}
-
-/* Callback when avahi changes state */
-static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
- gcc_unused void *userdata)
-{
- int reason;
- assert(c);
-
- /* Called whenever the client or server state changes */
- FormatDebug(avahi_domain, "Client changed to state %d", state);
-
- switch (state) {
- case AVAHI_CLIENT_S_RUNNING:
- LogDebug(avahi_domain, "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) {
- LogDefault(avahi_domain,
- "Client Disconnected, will reconnect shortly");
- if (avahiGroup) {
- avahi_entry_group_free(avahiGroup);
- avahiGroup = nullptr;
- }
- if (avahiClient)
- avahi_client_free(avahiClient);
- avahiClient =
- avahi_client_new(avahi_poll,
- AVAHI_CLIENT_NO_FAIL,
- avahiClientCallback, nullptr,
- &reason);
- if (!avahiClient) {
- FormatWarning(avahi_domain,
- "Could not reconnect: %s",
- avahi_strerror(reason));
- avahi_running = false;
- }
- } else {
- FormatWarning(avahi_domain,
- "Client failure: %s (terminal)",
- avahi_strerror(reason));
- avahi_running = false;
- }
- break;
-
- case AVAHI_CLIENT_S_COLLISION:
- LogDebug(avahi_domain, "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) {
- LogDebug(avahi_domain, "Resetting group");
- avahi_entry_group_reset(avahiGroup);
- }
-
- break;
-
- case AVAHI_CLIENT_S_REGISTERING:
- LogDebug(avahi_domain, "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) {
- LogDebug(avahi_domain, "Resetting group");
- avahi_entry_group_reset(avahiGroup);
- }
-
- break;
-
- case AVAHI_CLIENT_CONNECTING:
- LogDebug(avahi_domain, "Client is CONNECTING");
- break;
- }
-}
-
-void
-AvahiInit(EventLoop &loop, const char *serviceName)
-{
- LogDebug(avahi_domain, "Initializing interface");
-
- if (!avahi_is_valid_service_name(serviceName))
- FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName);
-
- avahiName = avahi_strdup(serviceName);
-
- avahi_running = true;
-
- avahi_poll = new MyAvahiPoll(loop);
-
- int error;
- avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
- avahiClientCallback, nullptr, &error);
-
- if (!avahiClient) {
- FormatError(avahi_domain, "Failed to create client: %s",
- avahi_strerror(error));
- AvahiDeinit();
- }
-}
-
-void
-AvahiDeinit(void)
-{
- LogDebug(avahi_domain, "Shutting down interface");
-
- if (avahiGroup) {
- avahi_entry_group_free(avahiGroup);
- avahiGroup = nullptr;
- }
-
- if (avahiClient) {
- avahi_client_free(avahiClient);
- avahiClient = nullptr;
- }
-
- delete avahi_poll;
- avahi_poll = nullptr;
-
- avahi_free(avahiName);
- avahiName = nullptr;
-}
diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx
deleted file mode 100644
index bb046350a..000000000
--- a/src/ZeroconfAvahi.hxx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 73e84fbc2..000000000
--- a/src/ZeroconfBonjour.cxx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ZeroconfBonjour.hxx"
-#include "ZeroconfInternal.hxx"
-#include "Listen.hxx"
-#include "event/SocketMonitor.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-#include <glib.h>
-
-#include <dns_sd.h>
-
-static constexpr Domain bonjour_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(gcc_unused DNSServiceRef sdRef,
- gcc_unused DNSServiceFlags flags,
- DNSServiceErrorType errorCode, const char *name,
- gcc_unused const char *regtype,
- gcc_unused const char *domain,
- gcc_unused void *context)
-{
- if (errorCode != kDNSServiceErr_NoError) {
- LogError(bonjour_domain,
- "Failed to register zeroconf service");
-
- bonjour_monitor->Cancel();
- } else {
- FormatDebug(bonjour_domain,
- "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, nullptr, nullptr,
- g_htons(listen_port), 0,
- nullptr,
- dnsRegisterCallback,
- nullptr);
-
- if (error != kDNSServiceErr_NoError) {
- LogError(bonjour_domain,
- "Failed to register zeroconf service");
-
- if (dnsReference) {
- DNSServiceRefDeallocate(dnsReference);
- dnsReference = nullptr;
- }
- return;
- }
-
- bonjour_monitor = new BonjourMonitor(loop, dnsReference);
-}
-
-void
-BonjourDeinit()
-{
- delete bonjour_monitor;
-}
diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx
deleted file mode 100644
index d91fe9a0d..000000000
--- a/src/ZeroconfBonjour.hxx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 7c9c973cc..000000000
--- a/src/ZeroconfGlue.cxx
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ZeroconfGlue.hxx"
-#include "ZeroconfAvahi.hxx"
-#include "ZeroconfBonjour.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#include "Listen.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-static constexpr Domain zeroconf_domain("zeroconf");
-
-/* 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) {
- LogWarning(zeroconf_domain,
- "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
deleted file mode 100644
index 2a291ce29..000000000
--- a/src/ZeroconfGlue.hxx
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 775e2dda2..000000000
--- a/src/ZeroconfInternal.hxx
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef 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/android/Context.cxx b/src/android/Context.cxx
new file mode 100644
index 000000000..f75e1503e
--- /dev/null
+++ b/src/android/Context.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Context.hxx"
+#include "java/Class.hxx"
+#include "java/File.hxx"
+#include "fs/AllocatedPath.hxx"
+
+AllocatedPath
+Context::GetCacheDir(JNIEnv *env) const
+{
+ assert(env != nullptr);
+
+ Java::Class cls(env, env->GetObjectClass(Get()));
+ jmethodID method = env->GetMethodID(cls, "getCacheDir",
+ "()Ljava/io/File;");
+ assert(method);
+
+ jobject file = env->CallObjectMethod(Get(), method);
+ if (file == nullptr) {
+ env->ExceptionClear();
+ return AllocatedPath::Null();
+ }
+
+ return Java::File::ToAbsolutePath(env, file);
+}
diff --git a/src/android/Context.hxx b/src/android/Context.hxx
new file mode 100644
index 000000000..b8a47777d
--- /dev/null
+++ b/src/android/Context.hxx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ANDROID_CONTEXT_HXX
+#define MPD_ANDROID_CONTEXT_HXX
+
+#include "java/Object.hxx"
+
+class AllocatedPath;
+
+class Context : public Java::Object {
+public:
+ Context(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
+
+ gcc_pure
+ AllocatedPath GetCacheDir(JNIEnv *env) const;
+};
+
+#endif
diff --git a/src/android/Environment.cxx b/src/android/Environment.cxx
new file mode 100644
index 000000000..9813b0b79
--- /dev/null
+++ b/src/android/Environment.cxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Environment.hxx"
+#include "java/Class.hxx"
+#include "java/String.hxx"
+#include "java/File.hxx"
+#include "util/StringUtil.hxx"
+#include "fs/AllocatedPath.hxx"
+
+namespace Environment {
+ static Java::TrivialClass cls;
+ static jmethodID getExternalStorageDirectory_method;
+ static jmethodID getExternalStoragePublicDirectory_method;
+};
+
+void
+Environment::Initialise(JNIEnv *env)
+{
+ cls.Find(env, "android/os/Environment");
+
+ getExternalStorageDirectory_method =
+ env->GetStaticMethodID(cls, "getExternalStorageDirectory",
+ "()Ljava/io/File;");
+
+ getExternalStoragePublicDirectory_method =
+ env->GetStaticMethodID(cls, "getExternalStoragePublicDirectory",
+ "(Ljava/lang/String;)Ljava/io/File;");
+}
+
+void
+Environment::Deinitialise(JNIEnv *env)
+{
+ cls.Clear(env);
+}
+
+AllocatedPath
+Environment::getExternalStorageDirectory()
+{
+ JNIEnv *env = Java::GetEnv();
+
+ jobject file =
+ env->CallStaticObjectMethod(cls,
+ getExternalStorageDirectory_method);
+ if (file == nullptr)
+ return AllocatedPath::Null();
+
+ return Java::File::ToAbsolutePath(env, file);
+}
+
+AllocatedPath
+Environment::getExternalStoragePublicDirectory(const char *type)
+{
+ if (getExternalStoragePublicDirectory_method == nullptr)
+ /* needs API level 8 */
+ return AllocatedPath::Null();
+
+ JNIEnv *env = Java::GetEnv();
+
+ Java::String type2(env, type);
+ jobject file = env->CallStaticObjectMethod(Environment::cls,
+ Environment::getExternalStoragePublicDirectory_method,
+ type2.Get());
+ if (file == nullptr)
+ return AllocatedPath::Null();
+
+ return Java::File::ToAbsolutePath(env, file);
+}
diff --git a/src/android/Environment.hxx b/src/android/Environment.hxx
new file mode 100644
index 000000000..5a54ea361
--- /dev/null
+++ b/src/android/Environment.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ANDROID_ENVIRONMENT_HXX
+#define MPD_ANDROID_ENVIRONMENT_HXX
+
+#include "Compiler.h"
+
+#include <jni.h>
+
+class AllocatedPath;
+
+namespace Environment {
+ void Initialise(JNIEnv *env);
+ void Deinitialise(JNIEnv *env);
+
+ /**
+ * Determine the mount point of the external SD card.
+ */
+ gcc_pure
+ AllocatedPath getExternalStorageDirectory();
+
+ gcc_pure
+ AllocatedPath getExternalStoragePublicDirectory(const char *type);
+};
+
+#endif
diff --git a/src/archive/ArchiveDomain.cxx b/src/archive/ArchiveDomain.cxx
new file mode 100644
index 000000000..4adf4a886
--- /dev/null
+++ b/src/archive/ArchiveDomain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ArchiveDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain archive_domain("archive");
diff --git a/src/archive/ArchiveDomain.hxx b/src/archive/ArchiveDomain.hxx
new file mode 100644
index 000000000..817ae5835
--- /dev/null
+++ b/src/archive/ArchiveDomain.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_DOMAIN_HXX
+#define MPD_ARCHIVE_DOMAIN_HXX
+
+extern const class Domain archive_domain;
+
+#endif
diff --git a/src/archive/ArchiveFile.hxx b/src/archive/ArchiveFile.hxx
new file mode 100644
index 000000000..473eef70b
--- /dev/null
+++ b/src/archive/ArchiveFile.hxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_FILE_HXX
+#define MPD_ARCHIVE_FILE_HXX
+
+class Mutex;
+class Cond;
+class Error;
+struct ArchivePlugin;
+class ArchiveVisitor;
+class InputStream;
+
+class ArchiveFile {
+public:
+ const ArchivePlugin &plugin;
+
+ ArchiveFile(const ArchivePlugin &_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 InputStream of a file within the archive.
+ *
+ * @param path the path within the archive
+ */
+ virtual InputStream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ Error &error) = 0;
+};
+
+#endif
diff --git a/src/archive/ArchiveList.cxx b/src/archive/ArchiveList.cxx
new file mode 100644
index 000000000..79c3a16fe
--- /dev/null
+++ b/src/archive/ArchiveList.cxx
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ArchiveList.hxx"
+#include "ArchivePlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "plugins/Bzip2ArchivePlugin.hxx"
+#include "plugins/Iso9660ArchivePlugin.hxx"
+#include "plugins/ZzipArchivePlugin.hxx"
+#include "util/Macros.hxx"
+
+#include <string.h>
+
+const ArchivePlugin *const archive_plugins[] = {
+#ifdef HAVE_BZ2
+ &bz2_archive_plugin,
+#endif
+#ifdef HAVE_ZZIP
+ &zzip_archive_plugin,
+#endif
+#ifdef HAVE_ISO9660
+ &iso9660_archive_plugin,
+#endif
+ nullptr
+};
+
+/** which plugins have been initialized successfully? */
+static bool archive_plugins_enabled[ARRAY_SIZE(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 ArchivePlugin *
+archive_plugin_from_suffix(const char *suffix)
+{
+ if (suffix == nullptr)
+ return nullptr;
+
+ archive_plugins_for_each_enabled(plugin)
+ if (plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix))
+ return plugin;
+
+ return nullptr;
+}
+
+const ArchivePlugin *
+archive_plugin_from_name(const char *name)
+{
+ archive_plugins_for_each_enabled(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return nullptr;
+}
+
+void archive_plugin_init_all(void)
+{
+ for (unsigned i = 0; archive_plugins[i] != nullptr; ++i) {
+ const ArchivePlugin *plugin = archive_plugins[i];
+ if (plugin->init == nullptr || archive_plugins[i]->init())
+ archive_plugins_enabled[i] = true;
+ }
+}
+
+void archive_plugin_deinit_all(void)
+{
+ archive_plugins_for_each_enabled(plugin)
+ if (plugin->finish != nullptr)
+ plugin->finish();
+}
+
diff --git a/src/archive/ArchiveList.hxx b/src/archive/ArchiveList.hxx
new file mode 100644
index 000000000..1f1b0ae96
--- /dev/null
+++ b/src/archive/ArchiveList.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_LIST_HXX
+#define MPD_ARCHIVE_LIST_HXX
+
+struct ArchivePlugin;
+
+extern const ArchivePlugin *const archive_plugins[];
+
+#define archive_plugins_for_each(plugin) \
+ for (const ArchivePlugin *plugin, \
+ *const*archive_plugin_iterator = &archive_plugins[0]; \
+ (plugin = *archive_plugin_iterator) != nullptr; \
+ ++archive_plugin_iterator)
+
+/* interface for using plugins */
+
+const ArchivePlugin *
+archive_plugin_from_suffix(const char *suffix);
+
+const ArchivePlugin *
+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/ArchiveLookup.cxx b/src/archive/ArchiveLookup.cxx
new file mode 100644
index 000000000..53730c504
--- /dev/null
+++ b/src/archive/ArchiveLookup.cxx
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "ArchiveLookup.hxx"
+#include "ArchiveDomain.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+gcc_pure
+static char *
+FindSlash(char *p, size_t i)
+{
+ for (; i > 0; --i)
+ if (p[i] == '/')
+ return p + i;
+
+ return nullptr;
+}
+
+gcc_pure
+static const char *
+FindSuffix(const char *p, const char *i)
+{
+ for (; i > p; --i) {
+ if (*i == '.')
+ return i + 1;
+ }
+
+ return nullptr;
+}
+
+bool
+archive_lookup(char *pathname, const char **archive,
+ const char **inpath, const char **suffix)
+{
+ size_t idx = strlen(pathname);
+
+ char *slash = nullptr;
+
+ while (true) {
+ //try to stat if its real directory
+ struct stat st_info;
+ if (stat(pathname, &st_info) == -1) {
+ if (errno != ENOTDIR) {
+ FormatErrno(archive_domain,
+ "Failed to stat %s", pathname);
+ return false;
+ }
+ } else {
+ //is something found ins original path (is not an archive)
+ if (slash == nullptr)
+ return false;
+
+ //its a file ?
+ if (S_ISREG(st_info.st_mode)) {
+ //so the upper should be file
+ *archive = pathname;
+ *inpath = slash + 1;
+
+ //try to get suffix
+ *suffix = FindSuffix(pathname, slash - 1);
+ return true;
+ } else {
+ FormatError(archive_domain,
+ "Not a regular file: %s",
+ pathname);
+ return false;
+ }
+ }
+
+ //find one dir up
+ if (slash != nullptr)
+ *slash = '/';
+
+ slash = FindSlash(pathname, idx - 1);
+ if (slash == nullptr)
+ return false;
+
+ *slash = 0;
+ idx = slash - pathname;
+ }
+}
+
diff --git a/src/archive/ArchiveLookup.hxx b/src/archive/ArchiveLookup.hxx
new file mode 100644
index 000000000..0c08951a9
--- /dev/null
+++ b/src/archive/ArchiveLookup.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_LOOKUP_HXX
+#define MPD_ARCHIVE_LOOKUP_HXX
+
+/**
+ *
+ * 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, const char **archive,
+ const char **inpath, const char **suffix);
+
+#endif
+
diff --git a/src/archive/ArchivePlugin.cxx b/src/archive/ArchivePlugin.cxx
new file mode 100644
index 000000000..67f469e08
--- /dev/null
+++ b/src/archive/ArchivePlugin.cxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ArchivePlugin.hxx"
+#include "ArchiveFile.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+ArchiveFile *
+archive_file_open(const ArchivePlugin *plugin, Path path,
+ Error &error)
+{
+ assert(plugin != nullptr);
+ assert(plugin->open != nullptr);
+ assert(!path.IsNull());
+
+ ArchiveFile *file = plugin->open(path, error);
+ assert((file == nullptr) == error.IsDefined());
+
+ return file;
+}
diff --git a/src/archive/ArchivePlugin.hxx b/src/archive/ArchivePlugin.hxx
new file mode 100644
index 000000000..eb24bbdf9
--- /dev/null
+++ b/src/archive/ArchivePlugin.hxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_PLUGIN_HXX
+#define MPD_ARCHIVE_PLUGIN_HXX
+
+class ArchiveFile;
+class Path;
+class Error;
+
+struct ArchivePlugin {
+ const char *name;
+
+ /**
+ * optional, set this to nullptr 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 nullptr 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 nullptr when opening fails
+ */
+ ArchiveFile *(*open)(Path path_fs, Error &error);
+
+ /**
+ * suffixes handled by this plugin.
+ * last element in these arrays must always be a nullptr
+ */
+ const char *const*suffixes;
+};
+
+ArchiveFile *
+archive_file_open(const ArchivePlugin *plugin, Path path,
+ Error &error);
+
+#endif
diff --git a/src/archive/ArchiveVisitor.hxx b/src/archive/ArchiveVisitor.hxx
new file mode 100644
index 000000000..6759695ca
--- /dev/null
+++ b/src/archive/ArchiveVisitor.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx
deleted file mode 100644
index d1e6b51af..000000000
--- a/src/archive/Bzip2ArchivePlugin.cxx
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/**
- * single bz2 archive handling (requires libbz2)
- */
-
-#include "config.h"
-#include "Bzip2ArchivePlugin.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "ArchiveVisitor.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/RefCount.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "fs/Traits.hxx"
-
-#include <bzlib.h>
-
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-
-#ifdef HAVE_OLDER_BZIP2
-#define BZ2_bzDecompressInit bzDecompressInit
-#define BZ2_bzDecompress bzDecompress
-#endif
-
-class Bzip2ArchiveFile final : public ArchiveFile {
-public:
- RefCount ref;
-
- std::string name;
- InputStream *const istream;
-
- Bzip2ArchiveFile(const char *path, InputStream *_is)
- :ArchiveFile(bz2_archive_plugin),
- name(PathTraits::GetBaseUTF8(path)),
- istream(_is) {
- // remove .bz2 suffix
- const size_t len = name.length();
- if (len > 4)
- name.erase(len - 4);
- }
-
- ~Bzip2ArchiveFile() {
- istream->Close();
- }
-
- void Ref() {
- ref.Increment();
- }
-
- void Unref() {
- if (!ref.Decrement())
- return;
-
- delete this;
- }
-
- virtual void Close() override {
- Unref();
- }
-
- virtual void Visit(ArchiveVisitor &visitor) override {
- visitor.VisitArchiveEntry(name.c_str());
- }
-
- virtual InputStream *OpenStream(const char *path,
- Mutex &mutex, Cond &cond,
- Error &error) override;
-};
-
-struct Bzip2InputStream {
- InputStream base;
-
- Bzip2ArchiveFile *archive;
-
- bool eof;
-
- bz_stream bzstream;
-
- char buffer[5000];
-
- Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri,
- Mutex &mutex, Cond &cond);
- ~Bzip2InputStream();
-
- bool Open(Error &error);
- void Close();
-};
-
-extern const InputPlugin bz2_inputplugin;
-
-static constexpr Domain bz2_domain("bz2");
-
-/* single archive handling allocation helpers */
-
-inline bool
-Bzip2InputStream::Open(Error &error)
-{
- 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) {
- error.Set(bz2_domain, 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, Error &error)
-{
- static Mutex mutex;
- static Cond cond;
- InputStream *is = InputStream::Open(pathname, mutex, cond, error);
- 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();
-}
-
-InputStream *
-Bzip2ArchiveFile::OpenStream(const char *path,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond);
- if (!bis->Open(error)) {
- delete bis;
- return nullptr;
- }
-
- return &bis->base;
-}
-
-static void
-bz2_is_close(InputStream *is)
-{
- Bzip2InputStream *bis = (Bzip2InputStream *)is;
-
- bis->Close();
- delete bis;
-}
-
-static bool
-bz2_fillbuffer(Bzip2InputStream *bis, Error &error)
-{
- size_t count;
- bz_stream *bzstream;
-
- bzstream = &bis->bzstream;
-
- if (bzstream->avail_in > 0)
- return true;
-
- count = bis->archive->istream->Read(bis->buffer, sizeof(bis->buffer),
- error);
- if (count == 0)
- return false;
-
- bzstream->next_in = bis->buffer;
- bzstream->avail_in = count;
- return true;
-}
-
-static size_t
-bz2_is_read(InputStream *is, void *ptr, size_t length,
- Error &error)
-{
- 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))
- return 0;
-
- bz_result = BZ2_bzDecompress(bzstream);
-
- if (bz_result == BZ_STREAM_END) {
- bis->eof = true;
- break;
- }
-
- if (bz_result != BZ_OK) {
- error.Set(bz2_domain, 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(InputStream *is)
-{
- Bzip2InputStream *bis = (Bzip2InputStream *)is;
-
- return bis->eof;
-}
-
-/* exported structures */
-
-static const char *const bz2_extensions[] = {
- "bz2",
- nullptr
-};
-
-const InputPlugin 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
deleted file mode 100644
index a7933a7a7..000000000
--- a/src/archive/Bzip2ArchivePlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index ad21d4a3d..000000000
--- a/src/archive/Iso9660ArchivePlugin.cxx
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/**
- * iso archive handling (requires cdio, and iso9660)
- */
-
-#include "config.h"
-#include "Iso9660ArchivePlugin.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "ArchiveVisitor.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/RefCount.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <cdio/cdio.h>
-#include <cdio/iso9660.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 InputStream *OpenStream(const char *path,
- Mutex &mutex, Cond &cond,
- Error &error) override;
-};
-
-extern const InputPlugin iso9660_input_plugin;
-
-static constexpr Domain iso9660_domain("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, Error &error)
-{
- /* open archive */
- auto iso = iso9660_open(pathname);
- if (iso == nullptr) {
- error.Format(iso9660_domain,
- "Failed to open ISO9660 file %s", pathname);
- return nullptr;
- }
-
- return new Iso9660ArchiveFile(iso);
-}
-
-void
-Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
-{
- Visit("/", visitor);
-}
-
-/* single archive handling */
-
-struct Iso9660InputStream {
- InputStream 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();
- }
-};
-
-InputStream *
-Iso9660ArchiveFile::OpenStream(const char *pathname,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- auto statbuf = iso9660_ifs_stat_translate(iso, pathname);
- if (statbuf == nullptr) {
- error.Format(iso9660_domain,
- "not found in the ISO file: %s", pathname);
- return nullptr;
- }
-
- Iso9660InputStream *iis =
- new Iso9660InputStream(*this, pathname, mutex, cond,
- statbuf);
- return &iis->base;
-}
-
-static void
-iso9660_input_close(InputStream *is)
-{
- Iso9660InputStream *iis = (Iso9660InputStream *)is;
-
- delete iis;
-}
-
-
-static size_t
-iso9660_input_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- Iso9660InputStream *iis = (Iso9660InputStream *)is;
- int readed = 0;
- int no_blocks, cur_block;
- size_t left_bytes = iis->statbuf->size - is->offset;
-
- size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
-
- if (left_bytes < size) {
- no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
- } else {
- no_blocks = size / ISO_BLOCKSIZE;
- }
- if (no_blocks > 0) {
-
- cur_block = is->offset / ISO_BLOCKSIZE;
-
- readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
- iis->statbuf->lsn + cur_block, no_blocks);
-
- if (readed != no_blocks * ISO_BLOCKSIZE) {
- error.Format(iso9660_domain,
- "error reading ISO file at lsn %lu",
- (unsigned long)cur_block);
- return 0;
- }
- if (left_bytes < size) {
- readed = left_bytes;
- }
-
- is->offset += readed;
- }
- return readed;
-}
-
-static bool
-iso9660_input_eof(InputStream *is)
-{
- return is->offset == is->size;
-}
-
-/* exported structures */
-
-static const char *const iso9660_archive_extensions[] = {
- "iso",
- nullptr
-};
-
-const InputPlugin 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
deleted file mode 100644
index 6fbab6159..000000000
--- a/src/archive/Iso9660ArchivePlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index d3e4cc837..000000000
--- a/src/archive/ZzipArchivePlugin.cxx
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/**
- * zip archive handling (requires zziplib)
- */
-
-#include "config.h"
-#include "ZzipArchivePlugin.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "ArchiveVisitor.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/RefCount.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <zzip/zzip.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 InputStream *OpenStream(const char *path,
- Mutex &mutex, Cond &cond,
- Error &error) override;
-};
-
-extern const InputPlugin zzip_input_plugin;
-
-static constexpr Domain zzip_domain("zzip");
-
-/* archive open && listing routine */
-
-static ArchiveFile *
-zzip_archive_open(const char *pathname, Error &error)
-{
- ZZIP_DIR *dir = zzip_dir_open(pathname, nullptr);
- if (dir == nullptr) {
- error.Format(zzip_domain, "Failed to open ZIP file %s",
- pathname);
- return nullptr;
- }
-
- 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 {
- InputStream 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();
- }
-};
-
-InputStream *
-ZzipArchiveFile::OpenStream(const char *pathname,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0);
- if (_file == nullptr) {
- error.Format(zzip_domain, "not found in the ZIP file: %s",
- pathname);
- return nullptr;
- }
-
- ZzipInputStream *zis =
- new ZzipInputStream(*this, pathname,
- mutex, cond,
- _file);
- return &zis->base;
-}
-
-static void
-zzip_input_close(InputStream *is)
-{
- ZzipInputStream *zis = (ZzipInputStream *)is;
-
- delete zis;
-}
-
-static size_t
-zzip_input_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- ZzipInputStream *zis = (ZzipInputStream *)is;
- int ret;
-
- ret = zzip_file_read(zis->file, ptr, size);
- if (ret < 0) {
- error.Set(zzip_domain, "zzip_file_read() has failed");
- return 0;
- }
-
- is->offset = zzip_tell(zis->file);
-
- return ret;
-}
-
-static bool
-zzip_input_eof(InputStream *is)
-{
- ZzipInputStream *zis = (ZzipInputStream *)is;
-
- return (InputPlugin::offset_type)zzip_tell(zis->file) == is->size;
-}
-
-static bool
-zzip_input_seek(InputStream *is, InputPlugin::offset_type offset,
- int whence, Error &error)
-{
- ZzipInputStream *zis = (ZzipInputStream *)is;
- zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
- if (ofs < 0) {
- error.Set(zzip_domain, "zzip_seek() has failed");
- return false;
- }
-
- is->offset = ofs;
- return true;
-}
-
-/* exported structures */
-
-static const char *const zzip_archive_extensions[] = {
- "zip",
- nullptr
-};
-
-const InputPlugin 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
deleted file mode 100644
index 4ba16849b..000000000
--- a/src/archive/ZzipArchivePlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ARCHIVE_ZZIP_HXX
-#define MPD_ARCHIVE_ZZIP_HXX
-
-extern const struct archive_plugin zzip_archive_plugin;
-
-#endif
diff --git a/src/archive/plugins/Bzip2ArchivePlugin.cxx b/src/archive/plugins/Bzip2ArchivePlugin.cxx
new file mode 100644
index 000000000..2b92049dd
--- /dev/null
+++ b/src/archive/plugins/Bzip2ArchivePlugin.cxx
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * single bz2 archive handling (requires libbz2)
+ */
+
+#include "config.h"
+#include "Bzip2ArchivePlugin.hxx"
+#include "../ArchivePlugin.hxx"
+#include "../ArchiveFile.hxx"
+#include "../ArchiveVisitor.hxx"
+#include "input/InputStream.hxx"
+#include "input/InputPlugin.hxx"
+#include "input/LocalOpen.hxx"
+#include "util/RefCount.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "fs/Traits.hxx"
+#include "fs/Path.hxx"
+
+#include <bzlib.h>
+
+#include <stddef.h>
+
+#ifdef HAVE_OLDER_BZIP2
+#define BZ2_bzDecompressInit bzDecompressInit
+#define BZ2_bzDecompress bzDecompress
+#endif
+
+class Bzip2ArchiveFile final : public ArchiveFile {
+public:
+ RefCount ref;
+
+ std::string name;
+ InputStream *const istream;
+
+ Bzip2ArchiveFile(Path path, InputStream *_is)
+ :ArchiveFile(bz2_archive_plugin),
+ name(PathTraitsFS::GetBase(path.c_str())),
+ istream(_is) {
+ // remove .bz2 suffix
+ const size_t len = name.length();
+ if (len > 4)
+ name.erase(len - 4);
+ }
+
+ ~Bzip2ArchiveFile() {
+ delete istream;
+ }
+
+ void Ref() {
+ ref.Increment();
+ }
+
+ void Unref() {
+ if (!ref.Decrement())
+ return;
+
+ delete this;
+ }
+
+ virtual void Close() override {
+ Unref();
+ }
+
+ virtual void Visit(ArchiveVisitor &visitor) override {
+ visitor.VisitArchiveEntry(name.c_str());
+ }
+
+ virtual InputStream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ Error &error) override;
+};
+
+struct Bzip2InputStream final : public InputStream {
+ Bzip2ArchiveFile *archive;
+
+ bool eof;
+
+ bz_stream bzstream;
+
+ char buffer[5000];
+
+ Bzip2InputStream(Bzip2ArchiveFile &context, const char *uri,
+ Mutex &mutex, Cond &cond);
+ ~Bzip2InputStream();
+
+ bool Open(Error &error);
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+};
+
+static constexpr Domain bz2_domain("bz2");
+
+/* single archive handling allocation helpers */
+
+inline bool
+Bzip2InputStream::Open(Error &error)
+{
+ 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) {
+ error.Set(bz2_domain, ret,
+ "BZ2_bzDecompressInit() has failed");
+ return false;
+ }
+
+ SetReady();
+ return true;
+}
+
+/* archive open && listing routine */
+
+static ArchiveFile *
+bz2_open(Path pathname, Error &error)
+{
+ static Mutex mutex;
+ static Cond cond;
+ InputStream *is = OpenLocalInputStream(pathname, mutex, cond, error);
+ if (is == nullptr)
+ return nullptr;
+
+ return new Bzip2ArchiveFile(pathname, is);
+}
+
+/* single archive handling */
+
+Bzip2InputStream::Bzip2InputStream(Bzip2ArchiveFile &_context,
+ const char *_uri,
+ Mutex &_mutex, Cond &_cond)
+ :InputStream(_uri, _mutex, _cond),
+ archive(&_context), eof(false)
+{
+ archive->Ref();
+}
+
+Bzip2InputStream::~Bzip2InputStream()
+{
+ BZ2_bzDecompressEnd(&bzstream);
+ archive->Unref();
+}
+
+InputStream *
+Bzip2ArchiveFile::OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ Bzip2InputStream *bis = new Bzip2InputStream(*this, path, mutex, cond);
+ if (!bis->Open(error)) {
+ delete bis;
+ return nullptr;
+ }
+
+ return bis;
+}
+
+static bool
+bz2_fillbuffer(Bzip2InputStream *bis, Error &error)
+{
+ size_t count;
+ bz_stream *bzstream;
+
+ bzstream = &bis->bzstream;
+
+ if (bzstream->avail_in > 0)
+ return true;
+
+ count = bis->archive->istream->Read(bis->buffer, sizeof(bis->buffer),
+ error);
+ if (count == 0)
+ return false;
+
+ bzstream->next_in = bis->buffer;
+ bzstream->avail_in = count;
+ return true;
+}
+
+size_t
+Bzip2InputStream::Read(void *ptr, size_t length, Error &error)
+{
+ int bz_result;
+ size_t nbytes = 0;
+
+ if (eof)
+ return 0;
+
+ bzstream.next_out = (char *)ptr;
+ bzstream.avail_out = length;
+
+ do {
+ if (!bz2_fillbuffer(this, error))
+ return 0;
+
+ bz_result = BZ2_bzDecompress(&bzstream);
+
+ if (bz_result == BZ_STREAM_END) {
+ eof = true;
+ break;
+ }
+
+ if (bz_result != BZ_OK) {
+ error.Set(bz2_domain, bz_result,
+ "BZ2_bzDecompress() has failed");
+ return 0;
+ }
+ } while (bzstream.avail_out == length);
+
+ nbytes = length - bzstream.avail_out;
+ offset += nbytes;
+
+ return nbytes;
+}
+
+bool
+Bzip2InputStream::IsEOF()
+{
+ return eof;
+}
+
+/* exported structures */
+
+static const char *const bz2_extensions[] = {
+ "bz2",
+ nullptr
+};
+
+const ArchivePlugin bz2_archive_plugin = {
+ "bz2",
+ nullptr,
+ nullptr,
+ bz2_open,
+ bz2_extensions,
+};
+
diff --git a/src/archive/plugins/Bzip2ArchivePlugin.hxx b/src/archive/plugins/Bzip2ArchivePlugin.hxx
new file mode 100644
index 000000000..1a0a578d1
--- /dev/null
+++ b/src/archive/plugins/Bzip2ArchivePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_BZ2_HXX
+#define MPD_ARCHIVE_BZ2_HXX
+
+struct ArchivePlugin;
+
+extern const ArchivePlugin bz2_archive_plugin;
+
+#endif
diff --git a/src/archive/plugins/Iso9660ArchivePlugin.cxx b/src/archive/plugins/Iso9660ArchivePlugin.cxx
new file mode 100644
index 000000000..ba415d3c5
--- /dev/null
+++ b/src/archive/plugins/Iso9660ArchivePlugin.cxx
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * iso archive handling (requires cdio, and iso9660)
+ */
+
+#include "config.h"
+#include "Iso9660ArchivePlugin.hxx"
+#include "../ArchivePlugin.hxx"
+#include "../ArchiveFile.hxx"
+#include "../ArchiveVisitor.hxx"
+#include "input/InputStream.hxx"
+#include "input/InputPlugin.hxx"
+#include "fs/Path.hxx"
+#include "util/RefCount.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <cdio/iso9660.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define CEILING(x, y) ((x+(y-1))/y)
+
+class Iso9660ArchiveFile final : public ArchiveFile {
+ RefCount ref;
+
+ iso9660_t *iso;
+
+public:
+ Iso9660ArchiveFile(iso9660_t *_iso)
+ :ArchiveFile(iso9660_archive_plugin), iso(_iso) {}
+
+ ~Iso9660ArchiveFile() {
+ iso9660_close(iso);
+ }
+
+ void Ref() {
+ ref.Increment();
+ }
+
+ void Unref() {
+ if (ref.Decrement())
+ delete this;
+ }
+
+ long SeekRead(void *ptr, lsn_t start, long int i_size) const {
+ return iso9660_iso_seek_read(iso, ptr, start, i_size);
+ }
+
+ void Visit(const char *path, ArchiveVisitor &visitor);
+
+ virtual void Close() override {
+ Unref();
+ }
+
+ virtual void Visit(ArchiveVisitor &visitor) override;
+
+ virtual InputStream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ Error &error) override;
+};
+
+static constexpr Domain iso9660_domain("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(Path pathname, Error &error)
+{
+ /* open archive */
+ auto iso = iso9660_open(pathname.c_str());
+ if (iso == nullptr) {
+ error.Format(iso9660_domain,
+ "Failed to open ISO9660 file %s",
+ pathname.c_str());
+ return nullptr;
+ }
+
+ return new Iso9660ArchiveFile(iso);
+}
+
+void
+Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
+{
+ Visit("/", visitor);
+}
+
+/* single archive handling */
+
+class Iso9660InputStream final : public InputStream {
+ Iso9660ArchiveFile &archive;
+
+ iso9660_stat_t *statbuf;
+
+public:
+ Iso9660InputStream(Iso9660ArchiveFile &_archive, const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ iso9660_stat_t *_statbuf)
+ :InputStream(_uri, _mutex, _cond),
+ archive(_archive), statbuf(_statbuf) {
+ size = statbuf->size;
+ SetReady();
+
+ archive.Ref();
+ }
+
+ ~Iso9660InputStream() {
+ free(statbuf);
+ archive.Unref();
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+};
+
+InputStream *
+Iso9660ArchiveFile::OpenStream(const char *pathname,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ auto statbuf = iso9660_ifs_stat_translate(iso, pathname);
+ if (statbuf == nullptr) {
+ error.Format(iso9660_domain,
+ "not found in the ISO file: %s", pathname);
+ return nullptr;
+ }
+
+ return new Iso9660InputStream(*this, pathname, mutex, cond,
+ statbuf);
+}
+
+size_t
+Iso9660InputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ int readed = 0;
+ int no_blocks, cur_block;
+ size_t left_bytes = statbuf->size - offset;
+
+ if (left_bytes < read_size) {
+ no_blocks = CEILING(left_bytes, ISO_BLOCKSIZE);
+ } else {
+ no_blocks = read_size / ISO_BLOCKSIZE;
+ }
+
+ if (no_blocks == 0)
+ return 0;
+
+ cur_block = offset / ISO_BLOCKSIZE;
+
+ readed = archive.SeekRead(ptr, statbuf->lsn + cur_block,
+ no_blocks);
+
+ if (readed != no_blocks * ISO_BLOCKSIZE) {
+ error.Format(iso9660_domain,
+ "error reading ISO file at lsn %lu",
+ (unsigned long)cur_block);
+ return 0;
+ }
+ if (left_bytes < read_size) {
+ readed = left_bytes;
+ }
+
+ offset += readed;
+ return readed;
+}
+
+bool
+Iso9660InputStream::IsEOF()
+{
+ return offset == size;
+}
+
+/* exported structures */
+
+static const char *const iso9660_archive_extensions[] = {
+ "iso",
+ nullptr
+};
+
+const ArchivePlugin iso9660_archive_plugin = {
+ "iso",
+ nullptr,
+ nullptr,
+ iso9660_archive_open,
+ iso9660_archive_extensions,
+};
diff --git a/src/archive/plugins/Iso9660ArchivePlugin.hxx b/src/archive/plugins/Iso9660ArchivePlugin.hxx
new file mode 100644
index 000000000..9335e83b3
--- /dev/null
+++ b/src/archive/plugins/Iso9660ArchivePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_ISO9660_HXX
+#define MPD_ARCHIVE_ISO9660_HXX
+
+struct ArchivePlugin;
+
+extern const ArchivePlugin iso9660_archive_plugin;
+
+#endif
diff --git a/src/archive/plugins/ZzipArchivePlugin.cxx b/src/archive/plugins/ZzipArchivePlugin.cxx
new file mode 100644
index 000000000..21cb693d8
--- /dev/null
+++ b/src/archive/plugins/ZzipArchivePlugin.cxx
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * zip archive handling (requires zziplib)
+ */
+
+#include "config.h"
+#include "ZzipArchivePlugin.hxx"
+#include "../ArchivePlugin.hxx"
+#include "../ArchiveFile.hxx"
+#include "../ArchiveVisitor.hxx"
+#include "input/InputStream.hxx"
+#include "input/InputPlugin.hxx"
+#include "fs/Path.hxx"
+#include "util/RefCount.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <zzip/zzip.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 InputStream *OpenStream(const char *path,
+ Mutex &mutex, Cond &cond,
+ Error &error) override;
+};
+
+static constexpr Domain zzip_domain("zzip");
+
+/* archive open && listing routine */
+
+static ArchiveFile *
+zzip_archive_open(Path pathname, Error &error)
+{
+ ZZIP_DIR *dir = zzip_dir_open(pathname.c_str(), nullptr);
+ if (dir == nullptr) {
+ error.Format(zzip_domain, "Failed to open ZIP file %s",
+ pathname.c_str());
+ return nullptr;
+ }
+
+ 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 final : public InputStream {
+ ZzipArchiveFile *archive;
+
+ ZZIP_FILE *file;
+
+ ZzipInputStream(ZzipArchiveFile &_archive, const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ ZZIP_FILE *_file)
+ :InputStream(_uri, _mutex, _cond),
+ archive(&_archive), file(_file) {
+ //we are seekable (but its not recommendent to do so)
+ seekable = true;
+
+ ZZIP_STAT z_stat;
+ zzip_file_stat(file, &z_stat);
+ size = z_stat.st_size;
+
+ SetReady();
+
+ archive->ref.Increment();
+ }
+
+ ~ZzipInputStream() {
+ zzip_file_close(file);
+ archive->Unref();
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+InputStream *
+ZzipArchiveFile::OpenStream(const char *pathname,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ ZZIP_FILE *_file = zzip_file_open(dir, pathname, 0);
+ if (_file == nullptr) {
+ error.Format(zzip_domain, "not found in the ZIP file: %s",
+ pathname);
+ return nullptr;
+ }
+
+ return new ZzipInputStream(*this, pathname,
+ mutex, cond,
+ _file);
+}
+
+size_t
+ZzipInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ int ret = zzip_file_read(file, ptr, read_size);
+ if (ret < 0) {
+ error.Set(zzip_domain, "zzip_file_read() has failed");
+ return 0;
+ }
+
+ offset = zzip_tell(file);
+ return ret;
+}
+
+bool
+ZzipInputStream::IsEOF()
+{
+ return offset_type(zzip_tell(file)) == size;
+}
+
+bool
+ZzipInputStream::Seek(offset_type new_offset, Error &error)
+{
+ zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
+ if (ofs < 0) {
+ error.Set(zzip_domain, "zzip_seek() has failed");
+ return false;
+ }
+
+ offset = ofs;
+ return true;
+}
+
+/* exported structures */
+
+static const char *const zzip_archive_extensions[] = {
+ "zip",
+ nullptr
+};
+
+const ArchivePlugin zzip_archive_plugin = {
+ "zzip",
+ nullptr,
+ nullptr,
+ zzip_archive_open,
+ zzip_archive_extensions,
+};
diff --git a/src/archive/plugins/ZzipArchivePlugin.hxx b/src/archive/plugins/ZzipArchivePlugin.hxx
new file mode 100644
index 000000000..cc92b7c52
--- /dev/null
+++ b/src/archive/plugins/ZzipArchivePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ARCHIVE_ZZIP_HXX
+#define MPD_ARCHIVE_ZZIP_HXX
+
+struct ArchivePlugin;
+
+extern const ArchivePlugin zzip_archive_plugin;
+
+#endif
diff --git a/src/check.h b/src/check.h
index 0642a4b91..efb4556d1 100644
--- a/src/check.h
+++ b/src/check.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/client/Client.cxx b/src/client/Client.cxx
new file mode 100644
index 000000000..01ead4645
--- /dev/null
+++ b/src/client/Client.cxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "util/Domain.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+
+const Domain client_domain("client");
+
+#ifdef ENABLE_DATABASE
+
+const Database *
+Client::GetDatabase(Error &error) const
+{
+ return partition.instance.GetDatabase(error);
+}
+
+const Storage *
+Client::GetStorage() const
+{
+ return partition.instance.storage;
+}
+
+#endif
diff --git a/src/client/Client.hxx b/src/client/Client.hxx
new file mode 100644
index 000000000..c0a940ded
--- /dev/null
+++ b/src/client/Client.hxx
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_H
+#define MPD_CLIENT_H
+
+#include "check.h"
+#include "ClientMessage.hxx"
+#include "command/CommandListBuilder.hxx"
+#include "event/FullyBufferedSocket.hxx"
+#include "event/TimeoutMonitor.hxx"
+#include "Compiler.h"
+
+#include <boost/intrusive/list.hpp>
+
+#include <set>
+#include <string>
+#include <list>
+
+#include <stddef.h>
+#include <stdarg.h>
+
+struct sockaddr;
+class EventLoop;
+class Path;
+struct Partition;
+class Database;
+class Storage;
+
+class Client final
+ : FullyBufferedSocket, TimeoutMonitor,
+ public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
+public:
+ Partition &partition;
+ struct playlist &playlist;
+ struct PlayerControl &player_control;
+
+ struct Disposer {
+ void operator()(Client *client) const {
+ delete client;
+ }
+ };
+
+ 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);
+
+ ~Client() {
+ if (FullyBufferedSocket::IsDefined())
+ FullyBufferedSocket::Close();
+ }
+
+ bool IsConnected() const {
+ return FullyBufferedSocket::IsDefined();
+ }
+
+ gcc_pure
+ bool IsExpired() const {
+ return !FullyBufferedSocket::IsDefined();
+ }
+
+ void Close();
+ void SetExpired();
+
+ using FullyBufferedSocket::Write;
+
+ /**
+ * returns the uid of the client process, or a negative value
+ * if the uid is unknown
+ */
+ int GetUID() const {
+ return uid;
+ }
+
+ /**
+ * Is this client running on the same machine, connected with
+ * a local (UNIX domain) socket?
+ */
+ bool IsLocal() const {
+ return uid >= 0;
+ }
+
+ unsigned GetPermission() const {
+ return permission;
+ }
+
+ void SetPermission(unsigned _permission) {
+ permission = _permission;
+ }
+
+ /**
+ * Send "idle" response to this client.
+ */
+ void IdleNotify();
+ void IdleAdd(unsigned flags);
+ bool IdleWait(unsigned flags);
+
+ enum class SubscribeResult {
+ /** success */
+ OK,
+
+ /** invalid channel name */
+ INVALID,
+
+ /** already subscribed to this channel */
+ ALREADY,
+
+ /** too many subscriptions */
+ FULL,
+ };
+
+ gcc_pure
+ bool IsSubscribed(const char *channel_name) const {
+ return subscriptions.find(channel_name) != subscriptions.end();
+ }
+
+ SubscribeResult Subscribe(const char *channel);
+ bool Unsubscribe(const char *channel);
+ void UnsubscribeAll();
+ bool PushMessage(const ClientMessage &msg);
+
+ /**
+ * 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 AllowFile(Path path_fs, Error &error) const;
+
+ /**
+ * Wrapper for Instance::GetDatabase().
+ */
+ gcc_pure
+ const Database *GetDatabase(Error &error) const;
+
+ gcc_pure
+ const Storage *GetStorage() const;
+
+private:
+ /* virtual methods from class BufferedSocket */
+ virtual InputResult OnSocketInput(void *data, size_t length) override;
+ virtual void OnSocketError(Error &&error) override;
+ virtual void OnSocketClosed() override;
+
+ /* virtual methods from class TimeoutMonitor */
+ virtual void OnTimeout() override;
+};
+
+void client_manager_init(void);
+
+void
+client_new(EventLoop &loop, Partition &partition,
+ int fd, const sockaddr *sa, size_t sa_length, int uid);
+
+/**
+ * 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_printf(2,3)
+void
+client_printf(Client &client, const char *fmt, ...);
+
+#endif
diff --git a/src/client/ClientEvent.cxx b/src/client/ClientEvent.cxx
new file mode 100644
index 000000000..fd9f24b0d
--- /dev/null
+++ b/src/client/ClientEvent.cxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+void
+Client::OnSocketError(Error &&error)
+{
+ FormatError(error, "error on client %d", num);
+
+ SetExpired();
+}
+
+void
+Client::OnSocketClosed()
+{
+ SetExpired();
+}
diff --git a/src/client/ClientExpire.cxx b/src/client/ClientExpire.cxx
new file mode 100644
index 000000000..5891756b6
--- /dev/null
+++ b/src/client/ClientExpire.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Log.hxx"
+
+void
+Client::SetExpired()
+{
+ if (IsExpired())
+ return;
+
+ FullyBufferedSocket::Close();
+ TimeoutMonitor::Schedule(0);
+}
+
+void
+Client::OnTimeout()
+{
+ if (!IsExpired()) {
+ assert(!idle_waiting);
+ FormatDebug(client_domain, "[%u] timeout", num);
+ }
+
+ Close();
+}
diff --git a/src/client/ClientFile.cxx b/src/client/ClientFile.cxx
new file mode 100644
index 000000000..3ea8034d2
--- /dev/null
+++ b/src/client/ClientFile.cxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Client.hxx"
+#include "protocol/Ack.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+bool
+Client::AllowFile(Path path_fs, Error &error) const
+{
+#ifdef WIN32
+ (void)path_fs;
+
+ error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
+ return false;
+#else
+ if (uid >= 0 && (uid_t)uid == geteuid())
+ /* always allow access if user runs his own MPD
+ instance */
+ return true;
+
+ if (uid < 0) {
+ /* unauthenticated client */
+ error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
+ return false;
+ }
+
+ struct stat st;
+ if (!StatFile(path_fs, st)) {
+ error.SetErrno();
+ return false;
+ }
+
+ if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
+ /* client is not owner */
+ error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
+ return false;
+ }
+
+ return true;
+#endif
+}
diff --git a/src/client/ClientGlobal.cxx b/src/client/ClientGlobal.cxx
new file mode 100644
index 000000000..8d90721e9
--- /dev/null
+++ b/src/client/ClientGlobal.cxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "config/ConfigGlobal.hxx"
+
+#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/client/ClientIdle.cxx b/src/client/ClientIdle.cxx
new file mode 100644
index 000000000..0b4fa5751
--- /dev/null
+++ b/src/client/ClientIdle.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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/client/ClientInternal.hxx b/src/client/ClientInternal.hxx
new file mode 100644
index 000000000..a819d64b8
--- /dev/null
+++ b/src/client/ClientInternal.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_INTERNAL_HXX
+#define MPD_CLIENT_INTERNAL_HXX
+
+#include "check.h"
+#include "Client.hxx"
+#include "command/CommandResult.hxx"
+
+static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16;
+static constexpr unsigned CLIENT_MAX_MESSAGES = 64;
+
+extern const class Domain client_domain;
+
+extern int client_timeout;
+extern size_t client_max_command_list_size;
+extern size_t client_max_output_buffer_size;
+
+CommandResult
+client_process_line(Client &client, char *line);
+
+#endif
diff --git a/src/client/ClientList.cxx b/src/client/ClientList.cxx
new file mode 100644
index 000000000..a1f286928
--- /dev/null
+++ b/src/client/ClientList.cxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientList.hxx"
+#include "ClientInternal.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+
+void
+ClientList::Remove(Client &client)
+{
+ assert(!list.empty());
+
+ list.erase(list.iterator_to(client));
+}
+
+void
+ClientList::CloseAll()
+{
+ list.clear_and_dispose(Client::Disposer());
+}
+
+void
+ClientList::IdleAdd(unsigned flags)
+{
+ assert(flags != 0);
+
+ for (auto &client : list)
+ client.IdleAdd(flags);
+}
diff --git a/src/client/ClientList.hxx b/src/client/ClientList.hxx
new file mode 100644
index 000000000..7d20a8737
--- /dev/null
+++ b/src/client/ClientList.hxx
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_LIST_HXX
+#define MPD_CLIENT_LIST_HXX
+
+#include "Client.hxx"
+
+class Client;
+
+class ClientList {
+ typedef boost::intrusive::list<Client,
+ boost::intrusive::constant_time_size<true>> List;
+
+ const unsigned max_size;
+
+ List list;
+
+public:
+ ClientList(unsigned _max_size)
+ :max_size(_max_size) {}
+ ~ClientList() {
+ CloseAll();
+ }
+
+ List::iterator begin() {
+ return list.begin();
+ }
+
+ List::iterator end() {
+ return list.end();
+ }
+
+ bool IsFull() const {
+ return list.size() >= max_size;
+ }
+
+ void Add(Client &client) {
+ list.push_front(client);
+ }
+
+ void Remove(Client &client);
+
+ void CloseAll();
+
+ void IdleAdd(unsigned flags);
+};
+
+#endif
diff --git a/src/client/ClientMessage.cxx b/src/client/ClientMessage.cxx
new file mode 100644
index 000000000..be6d2f007
--- /dev/null
+++ b/src/client/ClientMessage.cxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ClientMessage.hxx"
+#include "util/CharUtil.hxx"
+#include "Compiler.h"
+
+gcc_const
+static bool
+valid_channel_char(const char ch)
+{
+ return IsAlphaNumericASCII(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/client/ClientMessage.hxx b/src/client/ClientMessage.hxx
new file mode 100644
index 000000000..bc6a4cd41
--- /dev/null
+++ b/src/client/ClientMessage.hxx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLIENT_MESSAGE_H
+#define MPD_CLIENT_MESSAGE_H
+
+#include "Compiler.h"
+
+#include <string>
+
+#ifdef WIN32
+/* fuck WIN32! */
+#include <windows.h>
+#undef GetMessage
+#endif
+
+/**
+ * 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/client/ClientNew.cxx b/src/client/ClientNew.cxx
new file mode 100644
index 000000000..a080e9ec6
--- /dev/null
+++ b/src/client/ClientNew.cxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "ClientList.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "system/fd_util.h"
+#include "system/Resolver.hxx"
+#include "Permission.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+
+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;
+ const auto remote = sockaddr_to_string(sa, sa_length);
+
+ assert(fd >= 0);
+
+#ifdef HAVE_LIBWRAP
+ if (sa->sa_family != AF_UNIX) {
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ FormatWarning(client_domain,
+ "libwrap refused connection (libwrap=%s) from %s",
+ progname, remote.c_str());
+
+ close_socket(fd);
+ return;
+ }
+ }
+#endif /* HAVE_WRAP */
+
+ ClientList &client_list = *partition.instance.client_list;
+ if (client_list.IsFull()) {
+ LogWarning(client_domain, "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);
+
+ FormatInfo(client_domain, "[%u] opened from %s",
+ client->num, remote.c_str());
+}
+
+void
+Client::Close()
+{
+ partition.instance.client_list->Remove(*this);
+
+ SetExpired();
+
+ FormatInfo(client_domain, "[%u] closed", num);
+ delete this;
+}
diff --git a/src/client/ClientProcess.cxx b/src/client/ClientProcess.cxx
new file mode 100644
index 000000000..96099a91c
--- /dev/null
+++ b/src/client/ClientProcess.cxx
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "protocol/Result.hxx"
+#include "command/AllCommands.hxx"
+#include "Log.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 CommandResult
+client_process_command_list(Client &client, bool list_ok,
+ std::list<std::string> &&list)
+{
+ CommandResult ret = CommandResult::OK;
+ unsigned num = 0;
+
+ for (auto &&i : list) {
+ char *cmd = &*i.begin();
+
+ FormatDebug(client_domain, "process command \"%s\"", cmd);
+ ret = command_process(client, num++, cmd);
+ FormatDebug(client_domain, "command returned %i", ret);
+ if (ret != CommandResult::OK || client.IsExpired())
+ break;
+ else if (list_ok)
+ client_puts(client, "list_OK\n");
+ }
+
+ return ret;
+}
+
+CommandResult
+client_process_line(Client &client, char *line)
+{
+ CommandResult 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 CommandResult::OK;
+ } else if (client.idle_waiting) {
+ /* during idle mode, clients must not send anything
+ except "noidle" */
+ FormatWarning(client_domain,
+ "[%u] command \"%s\" during idle",
+ client.num, line);
+ return CommandResult::CLOSE;
+ }
+
+ if (client.cmd_list.IsActive()) {
+ if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
+ FormatDebug(client_domain,
+ "[%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));
+ FormatDebug(client_domain,
+ "[%u] process command "
+ "list returned %i", client.num, ret);
+
+ if (ret == CommandResult::CLOSE ||
+ client.IsExpired())
+ return CommandResult::CLOSE;
+
+ if (ret == CommandResult::OK)
+ command_success(client);
+
+ client.cmd_list.Reset();
+ } else {
+ if (!client.cmd_list.Add(line)) {
+ FormatWarning(client_domain,
+ "[%u] command list size "
+ "is larger than the max (%lu)",
+ client.num,
+ (unsigned long)client_max_command_list_size);
+ return CommandResult::CLOSE;
+ }
+
+ ret = CommandResult::OK;
+ }
+ } else {
+ if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
+ client.cmd_list.Begin(false);
+ ret = CommandResult::OK;
+ } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
+ client.cmd_list.Begin(true);
+ ret = CommandResult::OK;
+ } else {
+ FormatDebug(client_domain,
+ "[%u] process command \"%s\"",
+ client.num, line);
+ ret = command_process(client, 0, line);
+ FormatDebug(client_domain,
+ "[%u] command returned %i",
+ client.num, ret);
+
+ if (ret == CommandResult::CLOSE ||
+ client.IsExpired())
+ return CommandResult::CLOSE;
+
+ if (ret == CommandResult::OK)
+ command_success(client);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/client/ClientRead.cxx b/src/client/ClientRead.cxx
new file mode 100644
index 000000000..9cfb1271f
--- /dev/null
+++ b/src/client/ClientRead.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "event/Loop.hxx"
+#include "util/StringUtil.hxx"
+
+#include <string.h>
+
+BufferedSocket::InputResult
+Client::OnSocketInput(void *data, size_t length)
+{
+ char *p = (char *)data;
+ char *newline = (char *)memchr(p, '\n', length);
+ if (newline == nullptr)
+ return InputResult::MORE;
+
+ TimeoutMonitor::ScheduleSeconds(client_timeout);
+
+ BufferedSocket::ConsumeInput(newline + 1 - p);
+
+ /* skip whitespace at the end of the line */
+ char *end = StripRight(p, newline);
+
+ /* terminate the string at the end of the line */
+ *end = 0;
+
+ CommandResult result = client_process_line(*this, p);
+ switch (result) {
+ case CommandResult::OK:
+ case CommandResult::IDLE:
+ case CommandResult::ERROR:
+ break;
+
+ case CommandResult::KILL:
+ Close();
+ partition.instance.event_loop->Break();
+ return InputResult::CLOSED;
+
+ case CommandResult::FINISH:
+ if (Flush())
+ Close();
+ return InputResult::CLOSED;
+
+ case CommandResult::CLOSE:
+ Close();
+ return InputResult::CLOSED;
+ }
+
+ if (IsExpired()) {
+ Close();
+ return InputResult::CLOSED;
+ }
+
+ return InputResult::AGAIN;
+}
diff --git a/src/client/ClientSubscribe.cxx b/src/client/ClientSubscribe.cxx
new file mode 100644
index 000000000..8ea2e363b
--- /dev/null
+++ b/src/client/ClientSubscribe.cxx
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "Idle.hxx"
+
+#include <assert.h>
+
+Client::SubscribeResult
+Client::Subscribe(const char *channel)
+{
+ assert(channel != nullptr);
+
+ if (!client_message_valid_channel_name(channel))
+ return Client::SubscribeResult::INVALID;
+
+ if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
+ return Client::SubscribeResult::FULL;
+
+ auto r = subscriptions.insert(channel);
+ if (!r.second)
+ return Client::SubscribeResult::ALREADY;
+
+ ++num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ return Client::SubscribeResult::OK;
+}
+
+bool
+Client::Unsubscribe(const char *channel)
+{
+ const auto i = subscriptions.find(channel);
+ if (i == subscriptions.end())
+ return false;
+
+ assert(num_subscriptions > 0);
+
+ subscriptions.erase(i);
+ --num_subscriptions;
+
+ idle_add(IDLE_SUBSCRIPTION);
+
+ assert((num_subscriptions == 0) ==
+ subscriptions.empty());
+
+ return true;
+}
+
+void
+Client::UnsubscribeAll()
+{
+ subscriptions.clear();
+ num_subscriptions = 0;
+}
+
+bool
+Client::PushMessage(const ClientMessage &msg)
+{
+ if (messages.size() >= CLIENT_MAX_MESSAGES ||
+ !IsSubscribed(msg.GetChannel()))
+ return false;
+
+ if (messages.empty())
+ IdleAdd(IDLE_MESSAGE);
+
+ messages.push_back(msg);
+ return true;
+}
diff --git a/src/client/ClientWrite.cxx b/src/client/ClientWrite.cxx
new file mode 100644
index 000000000..b5d172a8d
--- /dev/null
+++ b/src/client/ClientWrite.cxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInternal.hxx"
+#include "util/FormatString.hxx"
+
+#include <string.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)
+{
+ char *p = FormatNewV(fmt, args);
+ client_write(client, p, strlen(p));
+ delete[] p;
+}
+
+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/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 36f6fb97c..6a4b18198 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,23 +20,27 @@
#include "config.h"
#include "AllCommands.hxx"
#include "QueueCommands.hxx"
+#include "TagCommands.hxx"
#include "PlayerCommands.hxx"
#include "PlaylistCommands.hxx"
+#include "StorageCommands.hxx"
#include "DatabaseCommands.hxx"
#include "FileCommands.hxx"
#include "OutputCommands.hxx"
#include "MessageCommands.hxx"
+#include "NeighborCommands.hxx"
#include "OtherCommands.hxx"
#include "Permission.hxx"
#include "tag/TagType.h"
#include "protocol/Result.hxx"
-#include "Client.hxx"
+#include "Partition.hxx"
+#include "client/Client.hxx"
#include "util/Tokenizer.hxx"
#include "util/Error.hxx"
#ifdef ENABLE_SQLITE
#include "StickerCommands.hxx"
-#include "StickerDatabase.hxx"
+#include "sticker/StickerDatabase.hxx"
#endif
#include <assert.h>
@@ -56,15 +60,15 @@ struct command {
unsigned permission;
int min;
int max;
- CommandResult (*handler)(Client &client, int argc, char **argv);
+ CommandResult (*handler)(Client &client, unsigned argc, char **argv);
};
/* don't be fooled, this is the command handler for "commands" command */
static CommandResult
-handle_commands(Client &client, int argc, char *argv[]);
+handle_commands(Client &client, unsigned argc, char *argv[]);
static CommandResult
-handle_not_commands(Client &client, int argc, char *argv[]);
+handle_not_commands(Client &client, unsigned argc, char *argv[]);
/**
* The command registry.
@@ -74,14 +78,18 @@ handle_not_commands(Client &client, int argc, char *argv[]);
static const struct command commands[] = {
{ "add", PERMISSION_ADD, 1, 1, handle_add },
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
+ { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
{ "channels", PERMISSION_READ, 0, 0, handle_channels },
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
+ { "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid },
{ "close", PERMISSION_NONE, -1, -1, handle_close },
{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
{ "config", PERMISSION_ADMIN, 0, 0, handle_config },
{ "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
+#ifdef ENABLE_DATABASE
{ "count", PERMISSION_READ, 2, -1, handle_count },
+#endif
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
@@ -89,13 +97,24 @@ static const struct command commands[] = {
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
+#ifdef ENABLE_DATABASE
{ "find", PERMISSION_READ, 2, -1, handle_find },
{ "findadd", PERMISSION_ADD, 2, -1, handle_findadd},
+#endif
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
+#ifdef ENABLE_DATABASE
{ "list", PERMISSION_READ, 1, -1, handle_list },
{ "listall", PERMISSION_READ, 0, 1, handle_listall },
{ "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
+#endif
+ { "listfiles", PERMISSION_READ, 0, 1, handle_listfiles },
+#ifdef ENABLE_DATABASE
+ { "listmounts", PERMISSION_READ, 0, 0, handle_listmounts },
+#endif
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ { "listneighbors", PERMISSION_READ, 0, 0, handle_listneighbors },
+#endif
{ "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
{ "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
@@ -103,6 +122,9 @@ static const struct command commands[] = {
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
+#ifdef ENABLE_DATABASE
+ { "mount", PERMISSION_ADMIN, 2, 2, handle_mount },
+#endif
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
@@ -128,6 +150,7 @@ static const struct command commands[] = {
{ "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
{ "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
+ { "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid },
{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },
{ "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
@@ -139,9 +162,11 @@ static const struct command commands[] = {
{ "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan },
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
+#ifdef ENABLE_DATABASE
{ "search", PERMISSION_READ, 2, -1, handle_search },
{ "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd },
{ "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl },
+#endif
{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
{ "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
@@ -160,6 +185,9 @@ static const struct command commands[] = {
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
+#ifdef ENABLE_DATABASE
+ { "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
+#endif
{ "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
{ "update", PERMISSION_CONTROL, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
@@ -169,20 +197,26 @@ static const struct command commands[] = {
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
static bool
-command_available(gcc_unused const struct command *cmd)
+command_available(gcc_unused const Partition &partition,
+ gcc_unused const struct command *cmd)
{
#ifdef ENABLE_SQLITE
if (strcmp(cmd->cmd, "sticker") == 0)
return sticker_enabled();
#endif
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (strcmp(cmd->cmd, "listneighbors") == 0)
+ return neighbor_commands_available(partition.instance);
+#endif
+
return true;
}
/* don't be fooled, this is the command handler for "commands" command */
static CommandResult
handle_commands(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
const unsigned permission = client.GetPermission();
const struct command *cmd;
@@ -191,7 +225,7 @@ handle_commands(Client &client,
cmd = &commands[i];
if (cmd->permission == (permission & cmd->permission) &&
- command_available(cmd))
+ command_available(client.partition, cmd))
client_printf(client, "command: %s\n", cmd->cmd);
}
@@ -200,7 +234,7 @@ handle_commands(Client &client,
static CommandResult
handle_not_commands(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
const unsigned permission = client.GetPermission();
const struct command *cmd;
@@ -252,10 +286,10 @@ command_lookup(const char *name)
static bool
command_check_request(const struct command *cmd, Client &client,
- unsigned permission, int argc, char *argv[])
+ unsigned permission, unsigned argc, char *argv[])
{
- int min = cmd->min + 1;
- int max = cmd->max + 1;
+ const unsigned min = cmd->min + 1;
+ const unsigned max = cmd->max + 1;
if (cmd->permission != (permission & cmd->permission)) {
command_error(client, ACK_ERROR_PERMISSION,
@@ -286,7 +320,7 @@ command_check_request(const struct command *cmd, Client &client,
static const struct command *
command_checked_lookup(Client &client, unsigned permission,
- int argc, char *argv[])
+ unsigned argc, char *argv[])
{
const struct command *cmd;
@@ -335,7 +369,9 @@ command_process(Client &client, unsigned num, char *line)
current_command = nullptr;
- return CommandResult::ERROR;
+ /* this client does not speak the MPD protocol; kick
+ the connection */
+ return CommandResult::FINISH;
}
unsigned argc = 1;
diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx
index 2be94c136..b7834a8de 100644
--- a/src/command/AllCommands.hxx
+++ b/src/command/AllCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx
index fc14d4a5d..89085fc68 100644
--- a/src/command/CommandError.cxx
+++ b/src/command/CommandError.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,14 +19,13 @@
#include "config.h"
#include "CommandError.hxx"
-#include "DatabaseError.hxx"
+#include "db/DatabaseError.hxx"
#include "protocol/Result.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
+#include <string.h>
#include <errno.h>
CommandResult
@@ -38,7 +37,7 @@ print_playlist_result(Client &client, PlaylistResult result)
case PlaylistResult::ERRNO:
command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(errno));
+ strerror(errno));
return CommandResult::ERROR;
case PlaylistResult::DENIED:
@@ -102,6 +101,7 @@ print_error(Client &client, const Error &error)
command_error(client, (ack)error.GetCode(),
"%s", error.GetMessage());
return CommandResult::ERROR;
+#ifdef ENABLE_DATABASE
} else if (error.IsDomain(db_domain)) {
switch ((enum db_error)error.GetCode()) {
case DB_DISABLED:
@@ -112,10 +112,15 @@ print_error(Client &client, const Error &error)
case DB_NOT_FOUND:
command_error(client, ACK_ERROR_NO_EXIST, "Not found");
return CommandResult::ERROR;
+
+ case DB_CONFLICT:
+ command_error(client, ACK_ERROR_ARG, "Conflict");
+ return CommandResult::ERROR;
}
+#endif
} else if (error.IsDomain(errno_domain)) {
command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(error.GetCode()));
+ strerror(error.GetCode()));
return CommandResult::ERROR;
}
diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx
index c7d3fff76..b48baa5bf 100644
--- a/src/command/CommandError.hxx
+++ b/src/command/CommandError.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx
index 4e0a8bd2a..477c246ff 100644
--- a/src/command/CommandListBuilder.cxx
+++ b/src/command/CommandListBuilder.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
#include "config.h"
#include "CommandListBuilder.hxx"
-#include "ClientInternal.hxx"
+#include "client/ClientInternal.hxx"
#include <string.h>
diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx
index a112ac33b..0747c4697 100644
--- a/src/command/CommandListBuilder.hxx
+++ b/src/command/CommandListBuilder.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/CommandResult.hxx b/src/command/CommandResult.hxx
index 9916b70cb..a2e968fb6 100644
--- a/src/command/CommandResult.hxx
+++ b/src/command/CommandResult.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index a7d2467b8..a3ea8d0ae 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,47 +19,59 @@
#include "config.h"
#include "DatabaseCommands.hxx"
-#include "DatabaseQueue.hxx"
-#include "DatabasePlaylist.hxx"
-#include "DatabasePrint.hxx"
-#include "DatabaseSelection.hxx"
+#include "db/DatabaseGlue.hxx"
+#include "db/DatabaseQueue.hxx"
+#include "db/DatabasePlaylist.hxx"
+#include "db/DatabasePrint.hxx"
+#include "db/Count.hxx"
+#include "db/Selection.hxx"
#include "CommandError.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "tag/Tag.hxx"
-#include "util/UriUtil.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "SongFilter.hxx"
#include "protocol/Result.hxx"
#include "BulkEdit.hxx"
-#include <assert.h>
#include <string.h>
CommandResult
-handle_lsinfo2(Client &client, int argc, char *argv[])
+handle_listfiles_db(Client &client, const char *uri)
{
- const char *uri;
+ const DatabaseSelection selection(uri, false);
- if (argc == 2)
- uri = argv[1];
- else
+ Error error;
+ if (!db_selection_print(client, selection, false, true, error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_lsinfo2(Client &client, unsigned argc, char *argv[])
+{
+ const char *const uri = argc == 2
+ ? argv[1]
/* default is root directory */
- uri = "";
+ : "";
const DatabaseSelection selection(uri, false);
Error error;
- if (!db_selection_print(client, selection, true, error))
+ if (!db_selection_print(client, selection, true, false, error))
return print_error(client, error);
return CommandResult::OK;
}
static CommandResult
-handle_match(Client &client, int argc, char *argv[], bool fold_case)
+handle_match(Client &client, unsigned argc, char *argv[], bool fold_case)
{
+ ConstBuffer<const char *> args(argv + 1, argc - 1);
+
SongFilter filter;
- if (!filter.Parse(argc - 1, argv + 1, fold_case)) {
+ if (!filter.Parse(args, fold_case)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return CommandResult::ERROR;
}
@@ -67,28 +79,30 @@ handle_match(Client &client, int argc, char *argv[], bool fold_case)
const DatabaseSelection selection("", true, &filter);
Error error;
- return db_selection_print(client, selection, true, error)
+ return db_selection_print(client, selection, true, false, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_find(Client &client, int argc, char *argv[])
+handle_find(Client &client, unsigned argc, char *argv[])
{
return handle_match(client, argc, argv, false);
}
CommandResult
-handle_search(Client &client, int argc, char *argv[])
+handle_search(Client &client, unsigned argc, char *argv[])
{
return handle_match(client, argc, argv, true);
}
static CommandResult
-handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
+handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case)
{
+ ConstBuffer<const char *> args(argv + 1, argc - 1);
+
SongFilter filter;
- if (!filter.Parse(argc - 1, argv + 1, fold_case)) {
+ if (!filter.Parse(args, fold_case)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return CommandResult::ERROR;
}
@@ -103,51 +117,73 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
}
CommandResult
-handle_findadd(Client &client, int argc, char *argv[])
+handle_findadd(Client &client, unsigned argc, char *argv[])
{
return handle_match_add(client, argc, argv, false);
}
CommandResult
-handle_searchadd(Client &client, int argc, char *argv[])
+handle_searchadd(Client &client, unsigned argc, char *argv[])
{
return handle_match_add(client, argc, argv, true);
}
CommandResult
-handle_searchaddpl(Client &client, int argc, char *argv[])
+handle_searchaddpl(Client &client, unsigned argc, char *argv[])
{
- const char *playlist = argv[1];
+ ConstBuffer<const char *> args(argv + 1, argc - 1);
+ const char *playlist = args.shift();
SongFilter filter;
- if (!filter.Parse(argc - 2, argv + 2, true)) {
+ if (!filter.Parse(args, true)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return CommandResult::ERROR;
}
Error error;
- return search_add_to_playlist("", playlist, &filter, error)
+ const Database *db = client.GetDatabase(error);
+ if (db == nullptr)
+ return print_error(client, error);
+
+ return search_add_to_playlist(*db, *client.GetStorage(),
+ "", playlist, &filter, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_count(Client &client, int argc, char *argv[])
+handle_count(Client &client, unsigned argc, char *argv[])
{
+ ConstBuffer<const char *> args(argv + 1, argc - 1);
+
+ TagType group = TAG_NUM_OF_ITEM_TYPES;
+ if (args.size >= 2 && strcmp(args[args.size - 2], "group") == 0) {
+ const char *s = args[args.size - 1];
+ group = tag_name_parse_i(s);
+ if (group == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", s);
+ return CommandResult::ERROR;
+ }
+
+ args.pop_back();
+ args.pop_back();
+ }
+
SongFilter filter;
- if (!filter.Parse(argc - 1, argv + 1, false)) {
+ if (!args.IsEmpty() && !filter.Parse(args, false)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return CommandResult::ERROR;
}
Error error;
- return searchStatsForSongsIn(client, "", &filter, error)
+ return PrintSongCount(client, "", &filter, group, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_listall(Client &client, gcc_unused int argc, char *argv[])
+handle_listall(Client &client, gcc_unused unsigned argc, char *argv[])
{
const char *directory = "";
@@ -155,30 +191,31 @@ handle_listall(Client &client, gcc_unused int argc, char *argv[])
directory = argv[1];
Error error;
- return printAllIn(client, directory, error)
+ return db_selection_print(client, DatabaseSelection(directory, true),
+ false, false, error)
? CommandResult::OK
: print_error(client, error);
}
CommandResult
-handle_list(Client &client, int argc, char *argv[])
+handle_list(Client &client, unsigned 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 CommandResult::ERROR;
- }
+ ConstBuffer<const char *> args(argv + 1, argc - 1);
+ const char *tag_name = args.shift();
+ unsigned tagType = locate_parse_type(tag_name);
- if (tagType == LOCATE_TAG_ANY_TYPE) {
+ if (tagType >= TAG_NUM_OF_ITEM_TYPES &&
+ tagType != LOCATE_TAG_FILE_TYPE) {
command_error(client, ACK_ERROR_ARG,
- "\"any\" is not a valid return tag type");
+ "Unknown tag type: %s", tag_name);
return CommandResult::ERROR;
}
- /* for compatibility with < 0.12.0 */
- SongFilter *filter;
- if (argc == 3) {
+ SongFilter *filter = nullptr;
+ uint32_t group_mask = 0;
+
+ if (args.size == 1) {
+ /* for compatibility with < 0.12.0 */
if (tagType != TAG_ALBUM) {
command_error(client, ACK_ERROR_ARG,
"should be \"%s\" for 3 arguments",
@@ -186,21 +223,45 @@ handle_list(Client &client, int argc, char *argv[])
return CommandResult::ERROR;
}
- filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]);
- } else if (argc > 2) {
+ filter = new SongFilter((unsigned)TAG_ARTIST, args.shift());
+ }
+
+ while (args.size >= 2 &&
+ strcmp(args[args.size - 2], "group") == 0) {
+ const char *s = args[args.size - 1];
+ TagType gt = tag_name_parse_i(s);
+ if (gt == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", s);
+ return CommandResult::ERROR;
+ }
+
+ group_mask |= 1u << unsigned(gt);
+
+ args.pop_back();
+ args.pop_back();
+ }
+
+ if (!args.IsEmpty()) {
filter = new SongFilter();
- if (!filter->Parse(argc - 2, argv + 2, false)) {
+ if (!filter->Parse(args, false)) {
delete filter;
command_error(client, ACK_ERROR_ARG,
"not able to parse args");
return CommandResult::ERROR;
}
- } else
- filter = nullptr;
+ }
+
+ if (tagType < TAG_NUM_OF_ITEM_TYPES &&
+ group_mask & (1u << tagType)) {
+ delete filter;
+ command_error(client, ACK_ERROR_ARG, "Conflicting group");
+ return CommandResult::ERROR;
+ }
Error error;
CommandResult ret =
- listAllUniqueTags(client, tagType, filter, error)
+ PrintUniqueTags(client, tagType, group_mask, filter, error)
? CommandResult::OK
: print_error(client, error);
@@ -210,7 +271,7 @@ handle_list(Client &client, int argc, char *argv[])
}
CommandResult
-handle_listallinfo(Client &client, gcc_unused int argc, char *argv[])
+handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[])
{
const char *directory = "";
@@ -218,7 +279,8 @@ handle_listallinfo(Client &client, gcc_unused int argc, char *argv[])
directory = argv[1];
Error error;
- return printInfoForAllIn(client, directory, error)
+ return db_selection_print(client, DatabaseSelection(directory, true),
+ true, false, error)
? CommandResult::OK
: print_error(client, error);
}
diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx
index 76b2ba817..7abf89e0c 100644
--- a/src/command/DatabaseCommands.hxx
+++ b/src/command/DatabaseCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,33 +25,36 @@
class Client;
CommandResult
-handle_lsinfo2(Client &client, int argc, char *argv[]);
+handle_listfiles_db(Client &client, const char *uri);
CommandResult
-handle_find(Client &client, int argc, char *argv[]);
+handle_lsinfo2(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_findadd(Client &client, int argc, char *argv[]);
+handle_find(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_search(Client &client, int argc, char *argv[]);
+handle_findadd(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_searchadd(Client &client, int argc, char *argv[]);
+handle_search(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_searchaddpl(Client &client, int argc, char *argv[]);
+handle_searchadd(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_count(Client &client, int argc, char *argv[]);
+handle_searchaddpl(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_listall(Client &client, int argc, char *argv[]);
+handle_count(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_list(Client &client, int argc, char *argv[]);
+handle_listall(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_listallinfo(Client &client, int argc, char *argv[]);
+handle_list(Client &client, unsigned argc, char *argv[]);
+
+CommandResult
+handle_listallinfo(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx
index eecc3102f..1b6a11cf5 100644
--- a/src/command/FileCommands.cxx
+++ b/src/command/FileCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,23 +17,109 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#define __STDC_FORMAT_MACROS /* for PRIu64 */
+
#include "config.h"
#include "FileCommands.hxx"
#include "CommandError.hxx"
#include "protocol/Ack.hxx"
#include "protocol/Result.hxx"
-#include "ClientFile.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "util/CharUtil.hxx"
+#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "tag/TagHandler.hxx"
#include "tag/ApeTag.hxx"
#include "tag/TagId3.hxx"
+#include "TagStream.hxx"
#include "TagFile.hxx"
-#include "Mapper.hxx"
+#include "storage/StorageInterface.hxx"
#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/DirectoryReader.hxx"
+#include "TimePrint.hxx"
+#include "ls.hxx"
#include <assert.h>
+#include <sys/stat.h>
+#include <inttypes.h> /* for PRIu64 */
+
+gcc_pure
+static bool
+SkipNameFS(const char *name_fs)
+{
+ return name_fs[0] == '.' &&
+ (name_fs[1] == 0 ||
+ (name_fs[1] == '.' && name_fs[2] == 0));
+}
+
+gcc_pure
+static bool
+skip_path(const char *name_fs)
+{
+ return strchr(name_fs, '\n') != nullptr;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+/* PRIu64 causes bogus compiler warning */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+#endif
+
+CommandResult
+handle_listfiles_local(Client &client, const char *path_utf8)
+{
+ const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
+ if (path_fs.IsNull()) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported file name");
+ return CommandResult::ERROR;
+ }
+
+ Error error;
+ if (!client.AllowFile(path_fs, error))
+ return print_error(client, error);
+
+ DirectoryReader reader(path_fs);
+ if (reader.HasFailed()) {
+ error.FormatErrno("Failed to open '%s'", path_utf8);
+ return print_error(client, error);
+ }
+
+ while (reader.ReadEntry()) {
+ const Path name_fs = reader.GetEntry();
+ if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs.c_str()))
+ continue;
+
+ std::string name_utf8 = name_fs.ToUTF8();
+ if (name_utf8.empty())
+ continue;
+
+ const AllocatedPath full_fs =
+ AllocatedPath::Build(path_fs, name_fs);
+ struct stat st;
+ if (!StatFile(full_fs, st, false))
+ continue;
+
+ if (S_ISREG(st.st_mode)) {
+ client_printf(client, "file: %s\n"
+ "size: %" PRIu64 "\n",
+ name_utf8.c_str(),
+ uint64_t(st.st_size));
+ } else if (S_ISDIR(st.st_mode))
+ client_printf(client, "directory: %s\n",
+ name_utf8.c_str());
+
+ time_print(client, "Last-Modified", st.st_mtime);
+ }
+
+ return CommandResult::OK;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
gcc_pure
static bool
@@ -80,19 +166,52 @@ static constexpr tag_handler print_comment_handler = {
print_pair,
};
+static CommandResult
+read_stream_comments(Client &client, const char *uri)
+{
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return CommandResult::ERROR;
+ }
+
+ if (!tag_stream_scan(uri, print_comment_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "Failed to load file");
+ return CommandResult::ERROR;
+ }
+
+ return CommandResult::OK;
+
+}
+
+static CommandResult
+read_file_comments(Client &client, const Path path_fs)
+{
+ if (!tag_file_scan(path_fs, print_comment_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "Failed to load file");
+ return CommandResult::ERROR;
+ }
+
+ tag_ape_scan2(path_fs, &print_comment_handler, &client);
+ tag_id3_scan(path_fs, &print_comment_handler, &client);
+
+ return CommandResult::OK;
+
+}
+
CommandResult
-handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
+handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[])
{
assert(argc == 2);
const char *const uri = argv[1];
- AllocatedPath path_fs = AllocatedPath::Null();
-
if (memcmp(uri, "file:///", 8) == 0) {
/* read comments from arbitrary local file */
const char *path_utf8 = uri + 7;
- path_fs = AllocatedPath::FromUTF8(path_utf8);
+ AllocatedPath path_fs = AllocatedPath::FromUTF8(path_utf8);
if (path_fs.IsNull()) {
command_error(client, ACK_ERROR_NO_EXIST,
"unsupported file name");
@@ -100,28 +219,41 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
}
Error error;
- if (!client_allow_file(client, path_fs, error))
+ if (!client.AllowFile(path_fs, error))
return print_error(client, error);
- } else if (*uri != '/') {
- path_fs = map_uri_fs(uri);
- if (path_fs.IsNull()) {
+
+ return read_file_comments(client, path_fs);
+ } else if (uri_has_scheme(uri)) {
+ return read_stream_comments(client, uri);
+ } else if (!PathTraitsUTF8::IsAbsolute(uri)) {
+#ifdef ENABLE_DATABASE
+ const Storage *storage = client.GetStorage();
+ if (storage == nullptr) {
+#endif
command_error(client, ACK_ERROR_NO_EXIST,
- "No such file");
+ "No database");
return CommandResult::ERROR;
+#ifdef ENABLE_DATABASE
}
- } else {
+
+ {
+ AllocatedPath path_fs = storage->MapFS(uri);
+ if (!path_fs.IsNull())
+ return read_file_comments(client, path_fs);
+ }
+
+ {
+ const std::string uri2 = storage->MapUTF8(uri);
+ if (uri_has_scheme(uri2.c_str()))
+ return read_stream_comments(client,
+ uri2.c_str());
+ }
+
command_error(client, ACK_ERROR_NO_EXIST, "No such file");
return CommandResult::ERROR;
- }
-
- if (!tag_file_scan(path_fs, &print_comment_handler, &client)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "Failed to load file");
+#endif
+ } else {
+ command_error(client, ACK_ERROR_NO_EXIST, "No such file");
return CommandResult::ERROR;
}
-
- tag_ape_scan2(path_fs, &print_comment_handler, &client);
- tag_id3_scan(path_fs, &print_comment_handler, &client);
-
- return CommandResult::OK;
}
diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx
index e184d6e30..62835a82c 100644
--- a/src/command/FileCommands.hxx
+++ b/src/command/FileCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,9 @@
class Client;
CommandResult
-handle_read_comments(Client &client, int argc, char *argv[]);
+handle_listfiles_local(Client &client, const char *path_utf8);
+
+CommandResult
+handle_read_comments(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx
index 7d9893e70..a86bdf30c 100644
--- a/src/command/MessageCommands.cxx
+++ b/src/command/MessageCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,11 @@
#include "config.h"
#include "MessageCommands.hxx"
-#include "Client.hxx"
-#include "ClientList.hxx"
+#include "client/Client.hxx"
+#include "client/ClientList.hxx"
#include "Instance.hxx"
-#include "Main.hxx"
+#include "Partition.hxx"
#include "protocol/Result.hxx"
-#include "protocol/ArgParser.hxx"
#include <set>
#include <string>
@@ -32,7 +31,7 @@
#include <assert.h>
CommandResult
-handle_subscribe(Client &client, gcc_unused int argc, char *argv[])
+handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[])
{
assert(argc == 2);
@@ -62,7 +61,7 @@ handle_subscribe(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[])
+handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[])
{
assert(argc == 2);
@@ -77,14 +76,14 @@ handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_channels(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_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 &c : *client.partition.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());
@@ -94,7 +93,7 @@ handle_channels(Client &client,
CommandResult
handle_read_messages(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
assert(argc == 1);
@@ -111,7 +110,7 @@ handle_read_messages(Client &client,
CommandResult
handle_send_message(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
assert(argc == 3);
@@ -123,8 +122,8 @@ handle_send_message(Client &client,
bool sent = false;
const ClientMessage msg(argv[1], argv[2]);
- for (const auto &c : *instance->client_list)
- if (c->PushMessage(msg))
+ for (auto &c : *client.partition.instance.client_list)
+ if (c.PushMessage(msg))
sent = true;
if (sent)
diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx
index 6a107f69d..ac8afe2fb 100644
--- a/src/command/MessageCommands.hxx
+++ b/src/command/MessageCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,18 +25,18 @@
class Client;
CommandResult
-handle_subscribe(Client &client, int argc, char *argv[]);
+handle_subscribe(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_unsubscribe(Client &client, int argc, char *argv[]);
+handle_unsubscribe(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_channels(Client &client, int argc, char *argv[]);
+handle_channels(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_read_messages(Client &client, int argc, char *argv[]);
+handle_read_messages(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_send_message(Client &client, int argc, char *argv[]);
+handle_send_message(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx
new file mode 100644
index 000000000..22e8adf9e
--- /dev/null
+++ b/src/command/NeighborCommands.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NeighborCommands.hxx"
+#include "client/Client.hxx"
+#include "Instance.hxx"
+#include "Partition.hxx"
+#include "protocol/Result.hxx"
+#include "neighbor/Glue.hxx"
+#include "neighbor/Info.hxx"
+
+#include <set>
+#include <string>
+
+#include <assert.h>
+
+bool
+neighbor_commands_available(const Instance &instance)
+{
+ return instance.neighbors != nullptr;
+}
+
+CommandResult
+handle_listneighbors(Client &client,
+ gcc_unused unsigned argc, gcc_unused char *argv[])
+{
+ const NeighborGlue *const neighbors =
+ client.partition.instance.neighbors;
+ if (neighbors == nullptr) {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "No neighbor plugin configured");
+ return CommandResult::ERROR;
+ }
+
+ for (const auto &i : neighbors->GetList())
+ client_printf(client,
+ "neighbor: %s\n"
+ "name: %s\n",
+ i.uri.c_str(),
+ i.display_name.c_str());
+ return CommandResult::OK;
+}
diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx
new file mode 100644
index 000000000..7fb309aeb
--- /dev/null
+++ b/src/command/NeighborCommands.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_COMMANDS_HXX
+#define MPD_NEIGHBOR_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+#include "Compiler.h"
+
+struct Instance;
+class Client;
+
+gcc_pure
+bool
+neighbor_commands_available(const Instance &instance);
+
+CommandResult
+handle_listneighbors(Client &client, unsigned argc, char *argv[]);
+
+#endif
diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx
index 7b2cb1331..a924f77b5 100644
--- a/src/command/OtherCommands.cxx
+++ b/src/command/OtherCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,33 +19,38 @@
#include "config.h"
#include "OtherCommands.hxx"
-#include "DatabaseCommands.hxx"
+#include "FileCommands.hxx"
+#include "StorageCommands.hxx"
#include "CommandError.hxx"
-#include "UpdateGlue.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
+#include "db/Uri.hxx"
+#include "storage/StorageInterface.hxx"
+#include "DetachedSong.hxx"
#include "SongPrint.hxx"
#include "TagPrint.hxx"
+#include "TagStream.hxx"
+#include "tag/TagHandler.hxx"
#include "TimePrint.hxx"
-#include "Mapper.hxx"
-#include "DecoderPrint.hxx"
+#include "decoder/DecoderPrint.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
-#include "Volume.hxx"
-#include "util/ASCII.hxx"
+#include "mixer/Volume.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "fs/AllocatedPath.hxx"
#include "Stats.hxx"
#include "Permission.hxx"
#include "PlaylistFile.hxx"
-#include "ClientFile.hxx"
-#include "Client.hxx"
+#include "db/PlaylistVector.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
#include "Idle.hxx"
-#ifdef ENABLE_SQLITE
-#include "StickerDatabase.hxx"
+#ifdef ENABLE_DATABASE
+#include "DatabaseCommands.hxx"
+#include "db/Interface.hxx"
+#include "db/update/Service.hxx"
#endif
#include <assert.h>
@@ -64,7 +69,7 @@ print_spl_list(Client &client, const PlaylistVector &list)
CommandResult
handle_urlhandlers(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
if (client.IsLocal())
client_puts(client, "handler: file://\n");
@@ -74,7 +79,7 @@ handle_urlhandlers(Client &client,
CommandResult
handle_decoders(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
decoder_list_print(client);
return CommandResult::OK;
@@ -82,7 +87,7 @@ handle_decoders(Client &client,
CommandResult
handle_tagtypes(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
tag_print_types(client);
return CommandResult::OK;
@@ -90,28 +95,74 @@ handle_tagtypes(Client &client,
CommandResult
handle_kill(gcc_unused Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
return CommandResult::KILL;
}
CommandResult
handle_close(gcc_unused Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
return CommandResult::FINISH;
}
+static void
+print_tag(TagType type, const char *value, void *ctx)
+{
+ Client &client = *(Client *)ctx;
+
+ tag_print(client, type, value);
+}
+
CommandResult
-handle_lsinfo(Client &client, int argc, char *argv[])
+handle_listfiles(Client &client, unsigned argc, char *argv[])
{
- const char *uri;
+ const char *const uri = argc == 2
+ ? argv[1]
+ /* default is root directory */
+ : "";
+
+ if (memcmp(uri, "file:///", 8) == 0)
+ /* list local directory */
+ return handle_listfiles_local(client, uri + 7);
+
+#ifdef ENABLE_DATABASE
+ if (uri_has_scheme(uri))
+ /* use storage plugin to list remote directory */
+ return handle_listfiles_storage(client, uri);
+
+ /* must be a path relative to the configured
+ music_directory */
+
+ if (client.partition.instance.storage != nullptr)
+ /* if we have a storage instance, obtain a list of
+ files from it */
+ return handle_listfiles_storage(client,
+ *client.partition.instance.storage,
+ uri);
+
+ /* fall back to entries from database if we have no storage */
+ return handle_listfiles_db(client, uri);
+#else
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
+}
+
+static constexpr tag_handler print_tag_handler = {
+ nullptr,
+ print_tag,
+ nullptr,
+};
- if (argc == 2)
- uri = argv[1];
- else
+CommandResult
+handle_lsinfo(Client &client, unsigned argc, char *argv[])
+{
+ const char *const uri = argc == 2
+ ? argv[1]
/* default is root directory */
- uri = "";
+ : "";
if (memcmp(uri, "file:///", 8) == 0) {
/* print information about an arbitrary local file */
@@ -125,55 +176,63 @@ handle_lsinfo(Client &client, int argc, char *argv[])
}
Error error;
- if (!client_allow_file(client, path_fs, error))
+ if (!client.AllowFile(path_fs, error))
return print_error(client, error);
- Song *song = Song::LoadFile(path_utf8, nullptr);
- if (song == nullptr) {
+ DetachedSong song(path_utf8);
+ if (!song.Update()) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such file");
return CommandResult::ERROR;
}
- song_print_info(client, *song);
- song->Free();
+ song_print_info(client, song);
return CommandResult::OK;
}
+ if (uri_has_scheme(uri)) {
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return CommandResult::ERROR;
+ }
+
+ if (!tag_stream_scan(uri, print_tag_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such file");
+ return CommandResult::ERROR;
+ }
+
+ return CommandResult::OK;
+ }
+
+#ifdef ENABLE_DATABASE
CommandResult result = handle_lsinfo2(client, argc, argv);
if (result != CommandResult::OK)
return result;
+#endif
if (isRootDirectory(uri)) {
Error error;
const auto &list = ListPlaylistFiles(error);
print_spl_list(client, list);
+ } else {
+#ifndef ENABLE_DATABASE
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
}
return CommandResult::OK;
}
-CommandResult
-handle_update(Client &client, gcc_unused int argc, char *argv[])
-{
- const char *path = "";
- unsigned ret;
+#ifdef ENABLE_DATABASE
- assert(argc <= 2);
- if (argc == 2) {
- path = argv[1];
-
- if (*path == 0 || strcmp(path, "/") == 0)
- /* backwards compatibility with MPD 0.15 */
- path = "";
- else if (!uri_safe_local(path)) {
- command_error(client, ACK_ERROR_ARG,
- "Malformed path");
- return CommandResult::ERROR;
- }
- }
-
- ret = update_enqueue(path, false);
+static CommandResult
+handle_update(Client &client, UpdateService &update,
+ const char *uri_utf8, bool discard)
+{
+ unsigned ret = update.Enqueue(uri_utf8, discard);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK;
@@ -184,36 +243,78 @@ handle_update(Client &client, gcc_unused int argc, char *argv[])
}
}
-CommandResult
-handle_rescan(Client &client, gcc_unused int argc, char *argv[])
+static CommandResult
+handle_update(Client &client, Database &db,
+ const char *uri_utf8, bool discard)
{
+ Error error;
+ unsigned id = db.Update(uri_utf8, discard, error);
+ if (id > 0) {
+ client_printf(client, "updating_db: %i\n", id);
+ return CommandResult::OK;
+ } else if (error.IsDefined()) {
+ return print_error(client, error);
+ } else {
+ /* Database::Update() has returned 0 without setting
+ the Error: the method is not implemented */
+ command_error(client, ACK_ERROR_NO_EXIST, "Not implemented");
+ return CommandResult::ERROR;
+ }
+}
+
+#endif
+
+static CommandResult
+handle_update(Client &client, unsigned argc, char *argv[], bool discard)
+{
+#ifdef ENABLE_DATABASE
const char *path = "";
- unsigned ret;
assert(argc <= 2);
if (argc == 2) {
path = argv[1];
- if (!uri_safe_local(path)) {
+ if (*path == 0 || strcmp(path, "/") == 0)
+ /* backwards compatibility with MPD 0.15 */
+ path = "";
+ else if (!uri_safe_local(path)) {
command_error(client, ACK_ERROR_ARG,
"Malformed path");
return CommandResult::ERROR;
}
}
- ret = update_enqueue(path, true);
- if (ret > 0) {
- client_printf(client, "updating_db: %i\n", ret);
- return CommandResult::OK;
- } else {
- command_error(client, ACK_ERROR_UPDATE_ALREADY,
- "already updating");
- return CommandResult::ERROR;
- }
+ UpdateService *update = client.partition.instance.update;
+ if (update != nullptr)
+ return handle_update(client, *update, path, discard);
+
+ Database *db = client.partition.instance.database;
+ if (db != nullptr)
+ return handle_update(client, *db, path, discard);
+#else
+ (void)argc;
+ (void)argv;
+ (void)discard;
+#endif
+
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+}
+
+CommandResult
+handle_update(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ return handle_update(client, argc, argv, false);
}
CommandResult
-handle_setvol(Client &client, gcc_unused int argc, char *argv[])
+handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ return handle_update(client, argc, argv, true);
+}
+
+CommandResult
+handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned level;
bool success;
@@ -226,7 +327,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
- success = volume_level_change(level);
+ success = volume_level_change(client.partition.outputs, level);
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
@@ -237,7 +338,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_volume(Client &client, gcc_unused int argc, char *argv[])
+handle_volume(Client &client, gcc_unused unsigned argc, char *argv[])
{
int relative;
if (!check_int(client, &relative, argv[1]))
@@ -248,7 +349,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
- const int old_volume = volume_level_get();
+ const int old_volume = volume_level_get(client.partition.outputs);
if (old_volume < 0) {
command_error(client, ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR;
@@ -260,7 +361,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
else if (new_volume > 100)
new_volume = 100;
- if (new_volume != old_volume && !volume_level_change(new_volume)) {
+ if (new_volume != old_volume &&
+ !volume_level_change(client.partition.outputs, new_volume)) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
return CommandResult::ERROR;
@@ -271,7 +373,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_stats(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
stats_print(client);
return CommandResult::OK;
@@ -279,13 +381,13 @@ handle_stats(Client &client,
CommandResult
handle_ping(gcc_unused Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
return CommandResult::OK;
}
CommandResult
-handle_password(Client &client, gcc_unused int argc, char *argv[])
+handle_password(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned permission = 0;
@@ -301,7 +403,7 @@ handle_password(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_config(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
if (!client.IsLocal()) {
command_error(client, ACK_ERROR_PERMISSION,
@@ -309,31 +411,33 @@ handle_config(Client &client,
return CommandResult::ERROR;
}
- const char *path = mapper_get_music_directory_utf8();
- if (path != nullptr)
- client_printf(client, "music_directory: %s\n", path);
+#ifdef ENABLE_DATABASE
+ const Storage *storage = client.GetStorage();
+ if (storage != nullptr) {
+ const auto path = storage->MapUTF8("");
+ client_printf(client, "music_directory: %s\n", path.c_str());
+ }
+#endif
return CommandResult::OK;
}
CommandResult
handle_idle(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_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 (StringEqualsCaseASCII(argv[i], idle_names[j])) {
- flags |= (1 << j);
- }
+ unsigned flags = 0;
+
+ for (unsigned i = 1; i < argc; ++i) {
+ unsigned event = idle_parse_name(argv[i]);
+ if (event == 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized idle event: %s",
+ argv[i]);
+ return CommandResult::ERROR;
}
+
+ flags |= event;
}
/* No argument means that the client wants to receive everything */
diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx
index 1a0b16ed1..7cfa35dfb 100644
--- a/src/command/OtherCommands.hxx
+++ b/src/command/OtherCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,48 +25,51 @@
class Client;
CommandResult
-handle_urlhandlers(Client &client, int argc, char *argv[]);
+handle_urlhandlers(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_decoders(Client &client, int argc, char *argv[]);
+handle_decoders(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_tagtypes(Client &client, int argc, char *argv[]);
+handle_tagtypes(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_kill(Client &client, int argc, char *argv[]);
+handle_kill(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_close(Client &client, int argc, char *argv[]);
+handle_close(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_lsinfo(Client &client, int argc, char *argv[]);
+handle_listfiles(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_update(Client &client, int argc, char *argv[]);
+handle_lsinfo(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_rescan(Client &client, int argc, char *argv[]);
+handle_update(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_setvol(Client &client, int argc, char *argv[]);
+handle_rescan(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_volume(Client &client, int argc, char *argv[]);
+handle_setvol(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_stats(Client &client, int argc, char *argv[]);
+handle_volume(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_ping(Client &client, int argc, char *argv[]);
+handle_stats(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_password(Client &client, int argc, char *argv[]);
+handle_ping(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_config(Client &client, int argc, char *argv[]);
+handle_password(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_idle(Client &client, int argc, char *argv[]);
+handle_config(Client &client, unsigned argc, char *argv[]);
+
+CommandResult
+handle_idle(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index e949448af..c69a0dd65 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,24 +19,21 @@
#include "config.h"
#include "OutputCommands.hxx"
-#include "OutputPrint.hxx"
-#include "OutputCommand.hxx"
+#include "output/OutputPrint.hxx"
+#include "output/OutputCommand.hxx"
#include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx"
-
-#include <string.h>
+#include "client/Client.hxx"
+#include "Partition.hxx"
CommandResult
-handle_enableoutput(Client &client, gcc_unused int argc, char *argv[])
+handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned device;
- bool ret;
-
if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR;
- ret = audio_output_enable_index(device);
- if (!ret) {
+ if (!audio_output_enable_index(client.partition.outputs, device)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return CommandResult::ERROR;
@@ -46,16 +43,13 @@ handle_enableoutput(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_disableoutput(Client &client, gcc_unused int argc, char *argv[])
+handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned device;
- bool ret;
-
if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR;
- ret = audio_output_disable_index(device);
- if (!ret) {
+ if (!audio_output_disable_index(client.partition.outputs, device)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return CommandResult::ERROR;
@@ -65,13 +59,13 @@ handle_disableoutput(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[])
+handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned device;
if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR;
- if (!audio_output_toggle_index(device)) {
+ if (!audio_output_toggle_index(client.partition.outputs, device)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return CommandResult::ERROR;
@@ -82,9 +76,9 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_devices(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
- printAudioDevices(client);
+ printAudioDevices(client, client.partition.outputs);
return CommandResult::OK;
}
diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx
index a5edc22e2..8d6be0511 100644
--- a/src/command/OutputCommands.hxx
+++ b/src/command/OutputCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,15 +25,15 @@
class Client;
CommandResult
-handle_enableoutput(Client &client, int argc, char *argv[]);
+handle_enableoutput(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_disableoutput(Client &client, int argc, char *argv[]);
+handle_disableoutput(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_toggleoutput(Client &client, int argc, char *argv[]);
+handle_toggleoutput(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_devices(Client &client, int argc, char *argv[]);
+handle_devices(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx
index 12c71dfd4..cd7f42289 100644
--- a/src/command/PlayerCommands.cxx
+++ b/src/command/PlayerCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,18 +20,21 @@
#include "config.h"
#include "PlayerCommands.hxx"
#include "CommandError.hxx"
-#include "Playlist.hxx"
+#include "queue/Playlist.hxx"
#include "PlaylistPrint.hxx"
-#include "UpdateGlue.hxx"
-#include "Client.hxx"
-#include "Volume.hxx"
-#include "OutputAll.hxx"
+#include "client/Client.hxx"
+#include "mixer/Volume.hxx"
#include "Partition.hxx"
+#include "Instance.hxx"
#include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx"
#include "AudioFormat.hxx"
#include "ReplayGainConfig.hxx"
+#ifdef ENABLE_DATABASE
+#include "db/update/Service.hxx"
+#endif
+
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -53,7 +56,7 @@
#define COMMAND_STATUS_UPDATING_DB "updating_db"
CommandResult
-handle_play(Client &client, int argc, char *argv[])
+handle_play(Client &client, unsigned argc, char *argv[])
{
int song = -1;
@@ -64,7 +67,7 @@ handle_play(Client &client, int argc, char *argv[])
}
CommandResult
-handle_playid(Client &client, int argc, char *argv[])
+handle_playid(Client &client, unsigned argc, char *argv[])
{
int id = -1;
@@ -77,7 +80,7 @@ handle_playid(Client &client, int argc, char *argv[])
CommandResult
handle_stop(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
client.partition.Stop();
return CommandResult::OK;
@@ -85,7 +88,7 @@ handle_stop(Client &client,
CommandResult
handle_currentsong(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
playlist_print_current(client, client.playlist);
return CommandResult::OK;
@@ -93,7 +96,7 @@ handle_currentsong(Client &client,
CommandResult
handle_pause(Client &client,
- int argc, char *argv[])
+ unsigned argc, char *argv[])
{
if (argc == 2) {
bool pause_flag;
@@ -109,10 +112,9 @@ handle_pause(Client &client,
CommandResult
handle_status(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
const char *state = nullptr;
- int updateJobId;
int song;
const auto player_status = client.player_control.GetStatus();
@@ -140,7 +142,7 @@ handle_status(Client &client,
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_MIXRAMPDB ": %f\n"
COMMAND_STATUS_STATE ": %s\n",
- volume_level_get(),
+ volume_level_get(client.partition.outputs),
playlist.GetRepeat(),
playlist.GetRandom(),
playlist.GetSingle(),
@@ -173,9 +175,11 @@ handle_status(Client &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.elapsed_time.RoundS(),
+ player_status.total_time.IsNegative()
+ ? 0u
+ : unsigned(player_status.total_time.RoundS()),
+ player_status.elapsed_time.ToDoubleS(),
player_status.bit_rate);
if (player_status.audio_format.IsDefined()) {
@@ -188,11 +192,17 @@ handle_status(Client &client,
}
}
- if ((updateJobId = isUpdatingDB())) {
+#ifdef ENABLE_DATABASE
+ const UpdateService *update_service = client.partition.instance.update;
+ unsigned updateJobId = update_service != nullptr
+ ? update_service->GetId()
+ : 0;
+ if (updateJobId != 0) {
client_printf(client,
COMMAND_STATUS_UPDATING_DB ": %i\n",
updateJobId);
}
+#endif
Error error = client.player_control.LockGetError();
if (error.IsDefined())
@@ -213,7 +223,7 @@ handle_status(Client &client,
CommandResult
handle_next(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
playlist &playlist = client.playlist;
@@ -230,14 +240,14 @@ handle_next(Client &client,
CommandResult
handle_previous(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
client.partition.PlayPrevious();
return CommandResult::OK;
}
CommandResult
-handle_repeat(Client &client, gcc_unused int argc, char *argv[])
+handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[])
{
bool status;
if (!check_bool(client, &status, argv[1]))
@@ -248,7 +258,7 @@ handle_repeat(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_single(Client &client, gcc_unused int argc, char *argv[])
+handle_single(Client &client, gcc_unused unsigned argc, char *argv[])
{
bool status;
if (!check_bool(client, &status, argv[1]))
@@ -259,7 +269,7 @@ handle_single(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_consume(Client &client, gcc_unused int argc, char *argv[])
+handle_consume(Client &client, gcc_unused unsigned argc, char *argv[])
{
bool status;
if (!check_bool(client, &status, argv[1]))
@@ -270,33 +280,34 @@ handle_consume(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_random(Client &client, gcc_unused int argc, char *argv[])
+handle_random(Client &client, gcc_unused unsigned argc, char *argv[])
{
bool status;
if (!check_bool(client, &status, argv[1]))
return CommandResult::ERROR;
client.partition.SetRandom(status);
- audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.partition.GetRandom()));
+ client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.partition.GetRandom()));
return CommandResult::OK;
}
CommandResult
handle_clearerror(gcc_unused Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
client.player_control.ClearError();
return CommandResult::OK;
}
CommandResult
-handle_seek(Client &client, gcc_unused int argc, char *argv[])
+handle_seek(Client &client, gcc_unused unsigned argc, char *argv[])
{
- unsigned song, seek_time;
+ unsigned song;
+ SongTime seek_time;
if (!check_unsigned(client, &song, argv[1]))
return CommandResult::ERROR;
- if (!check_unsigned(client, &seek_time, argv[2]))
+ if (!ParseCommandArg(client, seek_time, argv[2]))
return CommandResult::ERROR;
PlaylistResult result =
@@ -305,13 +316,14 @@ handle_seek(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_seekid(Client &client, gcc_unused int argc, char *argv[])
+handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[])
{
- unsigned id, seek_time;
+ unsigned id;
+ SongTime seek_time;
if (!check_unsigned(client, &id, argv[1]))
return CommandResult::ERROR;
- if (!check_unsigned(client, &seek_time, argv[2]))
+ if (!ParseCommandArg(client, seek_time, argv[2]))
return CommandResult::ERROR;
PlaylistResult result =
@@ -320,12 +332,12 @@ handle_seekid(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_seekcur(Client &client, gcc_unused int argc, char *argv[])
+handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[])
{
const char *p = argv[1];
bool relative = *p == '+' || *p == '-';
- int seek_time;
- if (!check_int(client, &seek_time, p))
+ SignedSongTime seek_time;
+ if (!ParseCommandArg(client, seek_time, p))
return CommandResult::ERROR;
PlaylistResult result =
@@ -334,7 +346,7 @@ handle_seekcur(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_crossfade(Client &client, gcc_unused int argc, char *argv[])
+handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned xfade_time;
@@ -346,7 +358,7 @@ handle_crossfade(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[])
+handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[])
{
float db;
@@ -358,7 +370,7 @@ handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[])
+handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[])
{
float delay_secs;
@@ -371,7 +383,7 @@ handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_replay_gain_mode(Client &client,
- gcc_unused int argc, char *argv[])
+ gcc_unused unsigned argc, char *argv[])
{
if (!replay_gain_set_mode_string(argv[1])) {
command_error(client, ACK_ERROR_ARG,
@@ -379,14 +391,13 @@ handle_replay_gain_mode(Client &client,
return CommandResult::ERROR;
}
- audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.playlist.queue.random));
-
+ client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.playlist.queue.random));
return CommandResult::OK;
}
CommandResult
handle_replay_gain_status(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
client_printf(client, "replay_gain_mode: %s\n",
replay_gain_get_mode_string());
diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx
index 8dad0855b..da7083f1e 100644
--- a/src/command/PlayerCommands.hxx
+++ b/src/command/PlayerCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,66 +25,66 @@
class Client;
CommandResult
-handle_play(Client &client, int argc, char *argv[]);
+handle_play(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playid(Client &client, int argc, char *argv[]);
+handle_playid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_stop(Client &client, int argc, char *argv[]);
+handle_stop(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_currentsong(Client &client, int argc, char *argv[]);
+handle_currentsong(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_pause(Client &client, int argc, char *argv[]);
+handle_pause(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_status(Client &client, int argc, char *argv[]);
+handle_status(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_next(Client &client, int argc, char *argv[]);
+handle_next(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_previous(Client &client, int argc, char *avg[]);
+handle_previous(Client &client, unsigned argc, char *avg[]);
CommandResult
-handle_repeat(Client &client, int argc, char *argv[]);
+handle_repeat(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_single(Client &client, int argc, char *argv[]);
+handle_single(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_consume(Client &client, int argc, char *argv[]);
+handle_consume(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_random(Client &client, int argc, char *argv[]);
+handle_random(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_clearerror(Client &client, int argc, char *argv[]);
+handle_clearerror(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_seek(Client &client, int argc, char *argv[]);
+handle_seek(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_seekid(Client &client, int argc, char *argv[]);
+handle_seekid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_seekcur(Client &client, int argc, char *argv[]);
+handle_seekcur(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_crossfade(Client &client, int argc, char *argv[]);
+handle_crossfade(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_mixrampdb(Client &client, int argc, char *argv[]);
+handle_mixrampdb(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_mixrampdelay(Client &client, int argc, char *argv[]);
+handle_mixrampdelay(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_replay_gain_mode(Client &client, int argc, char *argv[]);
+handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_replay_gain_status(Client &client, int argc, char *argv[]);
+handle_replay_gain_status(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx
index c4441293e..c2b18064c 100644
--- a/src/command/PlaylistCommands.cxx
+++ b/src/command/PlaylistCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,26 +19,25 @@
#include "config.h"
#include "PlaylistCommands.hxx"
-#include "DatabasePlaylist.hxx"
+#include "db/DatabasePlaylist.hxx"
#include "CommandError.hxx"
#include "PlaylistPrint.hxx"
#include "PlaylistSave.hxx"
#include "PlaylistFile.hxx"
-#include "PlaylistVector.hxx"
-#include "PlaylistQueue.hxx"
+#include "db/PlaylistVector.hxx"
+#include "SongLoader.hxx"
#include "BulkEdit.hxx"
+#include "playlist/PlaylistQueue.hxx"
+#include "playlist/Print.hxx"
+#include "queue/Playlist.hxx"
#include "TimePrint.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
-#include "Playlist.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
-#include <assert.h>
-#include <stdlib.h>
-
static void
print_spl_list(Client &client, const PlaylistVector &list)
{
@@ -51,14 +50,14 @@ print_spl_list(Client &client, const PlaylistVector &list)
}
CommandResult
-handle_save(Client &client, gcc_unused int argc, char *argv[])
+handle_save(Client &client, gcc_unused unsigned argc, char *argv[])
{
PlaylistResult result = spl_save_playlist(argv[1], client.playlist);
return print_playlist_result(client, result);
}
CommandResult
-handle_load(Client &client, int argc, char *argv[])
+handle_load(Client &client, unsigned argc, char *argv[])
{
unsigned start_index, end_index;
@@ -70,36 +69,19 @@ handle_load(Client &client, int argc, char *argv[])
const ScopeBulkEdit bulk_edit(client.partition);
- const PlaylistResult result =
- playlist_open_into_queue(argv[1],
- start_index, end_index,
- client.playlist,
- client.player_control, true);
- if (result != PlaylistResult::NO_SUCH_LIST)
- return print_playlist_result(client, result);
-
Error error;
- if (playlist_load_spl(client.playlist, client.player_control,
- argv[1], start_index, end_index,
- error))
- return CommandResult::OK;
-
- if (error.IsDomain(playlist_domain) &&
- PlaylistResult(error.GetCode()) == PlaylistResult::BAD_NAME) {
- /* the message for BAD_NAME is confusing when the
- client wants to load a playlist file from the music
- directory; patch the Error object to show "no such
- playlist" instead */
- Error error2(playlist_domain, int(PlaylistResult::NO_SUCH_LIST),
- error.GetMessage());
- error = std::move(error2);
- }
+ const SongLoader loader(client);
+ if (!playlist_open_into_queue(argv[1],
+ start_index, end_index,
+ client.playlist,
+ client.player_control, loader, error))
+ return print_error(client, error);
- return print_error(client, error);
+ return CommandResult::OK;
}
CommandResult
-handle_listplaylist(Client &client, gcc_unused int argc, char *argv[])
+handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[])
{
if (playlist_file_print(client, argv[1], false))
return CommandResult::OK;
@@ -112,7 +94,7 @@ handle_listplaylist(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_listplaylistinfo(Client &client,
- gcc_unused int argc, char *argv[])
+ gcc_unused unsigned argc, char *argv[])
{
if (playlist_file_print(client, argv[1], true))
return CommandResult::OK;
@@ -124,7 +106,7 @@ handle_listplaylistinfo(Client &client,
}
CommandResult
-handle_rm(Client &client, gcc_unused int argc, char *argv[])
+handle_rm(Client &client, gcc_unused unsigned argc, char *argv[])
{
Error error;
return spl_delete(argv[1], error)
@@ -133,7 +115,7 @@ handle_rm(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_rename(Client &client, gcc_unused int argc, char *argv[])
+handle_rename(Client &client, gcc_unused unsigned argc, char *argv[])
{
Error error;
return spl_rename(argv[1], argv[2], error)
@@ -143,7 +125,7 @@ handle_rename(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_playlistdelete(Client &client,
- gcc_unused int argc, char *argv[]) {
+ gcc_unused unsigned argc, char *argv[]) {
char *playlist = argv[1];
unsigned from;
@@ -157,7 +139,7 @@ handle_playlistdelete(Client &client,
}
CommandResult
-handle_playlistmove(Client &client, gcc_unused int argc, char *argv[])
+handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[])
{
char *playlist = argv[1];
unsigned from, to;
@@ -174,7 +156,7 @@ handle_playlistmove(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_playlistclear(Client &client, gcc_unused int argc, char *argv[])
+handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[])
{
Error error;
return spl_clear(argv[1], error)
@@ -183,7 +165,7 @@ handle_playlistclear(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_playlistadd(Client &client, gcc_unused int argc, char *argv[])
+handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[])
{
char *playlist = argv[1];
char *uri = argv[2];
@@ -191,16 +173,21 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[])
bool success;
Error error;
if (uri_has_scheme(uri)) {
- if (!uri_supported_scheme(uri)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported URI scheme");
- return CommandResult::ERROR;
- }
-
- success = spl_append_uri(uri, playlist, error);
- } else
- success = search_add_to_playlist(uri, playlist, nullptr,
+ const SongLoader loader(client);
+ success = spl_append_uri(playlist, loader, uri, error);
+ } else {
+#ifdef ENABLE_DATABASE
+ const Database *db = client.GetDatabase(error);
+ if (db == nullptr)
+ return print_error(client, error);
+
+ success = search_add_to_playlist(*db, *client.GetStorage(),
+ uri, playlist, nullptr,
error);
+#else
+ success = false;
+#endif
+ }
if (!success && !error.IsDefined()) {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -213,7 +200,7 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_listplaylists(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
Error error;
const auto list = ListPlaylistFiles(error);
diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx
index 802d6ff2f..fba4e1318 100644
--- a/src/command/PlaylistCommands.hxx
+++ b/src/command/PlaylistCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,36 +25,36 @@
class Client;
CommandResult
-handle_save(Client &client, int argc, char *argv[]);
+handle_save(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_load(Client &client, int argc, char *argv[]);
+handle_load(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_listplaylist(Client &client, int argc, char *argv[]);
+handle_listplaylist(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_listplaylistinfo(Client &client, int argc, char *argv[]);
+handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_rm(Client &client, int argc, char *argv[]);
+handle_rm(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_rename(Client &client, int argc, char *argv[]);
+handle_rename(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistdelete(Client &client, int argc, char *argv[]);
+handle_playlistdelete(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistmove(Client &client, int argc, char *argv[]);
+handle_playlistmove(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistclear(Client &client, int argc, char *argv[]);
+handle_playlistclear(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistadd(Client &client, int argc, char *argv[]);
+handle_playlistadd(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_listplaylists(Client &client, int argc, char *argv[]);
+handle_listplaylists(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx
index a987e1bc9..d0b789eb1 100644
--- a/src/command/QueueCommands.cxx
+++ b/src/command/QueueCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,19 +20,21 @@
#include "config.h"
#include "QueueCommands.hxx"
#include "CommandError.hxx"
-#include "DatabaseQueue.hxx"
+#include "db/DatabaseQueue.hxx"
+#include "db/Selection.hxx"
#include "SongFilter.hxx"
-#include "DatabaseSelection.hxx"
-#include "Playlist.hxx"
+#include "SongLoader.hxx"
+#include "queue/Playlist.hxx"
#include "PlaylistPrint.hxx"
-#include "ClientFile.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "Partition.hxx"
#include "BulkEdit.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/UriUtil.hxx"
+#include "util/NumberParser.hxx"
#include "util/Error.hxx"
#include "fs/AllocatedPath.hxx"
@@ -40,40 +42,49 @@
#include <string.h>
-CommandResult
-handle_add(Client &client, gcc_unused int argc, char *argv[])
+static const char *
+translate_uri(Client &client, const char *uri)
{
- char *uri = argv[1];
+ if (memcmp(uri, "file:///", 8) == 0)
+ /* drop the "file://", leave only an absolute path
+ (starting with a slash) */
+ return uri + 7;
+
+ if (PathTraitsUTF8::IsAbsolute(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST, "Malformed URI");
+ return nullptr;
+ }
- if (memcmp(uri, "file:///", 8) == 0) {
- const char *path_utf8 = uri + 7;
- const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
+ return uri;
+}
- if (path_fs.IsNull()) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported file name");
- return CommandResult::ERROR;
- }
+CommandResult
+handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ const char *uri = argv[1];
+ if (memcmp(uri, "/", 2) == 0)
+ /* this URI is malformed, but some clients are buggy
+ and use "add /" to add the whole database, which
+ was never intended to work, but once did; in order
+ to retain backwards compatibility, work around this
+ here */
+ uri = "";
+
+ uri = translate_uri(client, uri);
+ if (uri == nullptr)
+ return CommandResult::ERROR;
+ if (uri_has_scheme(uri) || PathTraitsUTF8::IsAbsolute(uri)) {
+ const SongLoader loader(client);
Error error;
- if (!client_allow_file(client, path_fs, error))
+ unsigned id = client.partition.AppendURI(loader, uri, error);
+ if (id == 0)
return print_error(client, error);
- auto 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 CommandResult::ERROR;
- }
-
- auto result = client.partition.AppendURI(uri);
- return print_playlist_result(client, result);
+ return CommandResult::OK;
}
+#ifdef ENABLE_DATABASE
const ScopeBulkEdit bulk_edit(client.partition);
const DatabaseSelection selection(uri, true);
@@ -81,48 +92,30 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
return AddFromDatabase(client.partition, selection, error)
? CommandResult::OK
: print_error(client, error);
+#else
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
}
CommandResult
-handle_addid(Client &client, int argc, char *argv[])
+handle_addid(Client &client, unsigned argc, char *argv[])
{
- char *uri = argv[1];
- unsigned added_id;
- PlaylistResult result;
-
- if (memcmp(uri, "file:///", 8) == 0) {
- const char *path_utf8 = uri + 7;
- const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
-
- if (path_fs.IsNull()) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported file name");
- return CommandResult::ERROR;
- }
-
- Error error;
- 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 CommandResult::ERROR;
- }
-
- result = client.partition.AppendURI(uri, &added_id);
- }
+ const char *const uri = translate_uri(client, argv[1]);
+ if (uri == nullptr)
+ return CommandResult::ERROR;
- if (result != PlaylistResult::SUCCESS)
- return print_playlist_result(client, result);
+ const SongLoader loader(client);
+ Error error;
+ unsigned added_id = client.partition.AppendURI(loader, uri, error);
+ if (added_id == 0)
+ return print_error(client, error);
if (argc == 3) {
unsigned to;
if (!check_unsigned(client, &to, argv[2]))
return CommandResult::ERROR;
- result = client.partition.MoveId(added_id, to);
+ PlaylistResult result = client.partition.MoveId(added_id, to);
if (result != PlaylistResult::SUCCESS) {
CommandResult ret =
print_playlist_result(client, result);
@@ -135,8 +128,61 @@ handle_addid(Client &client, int argc, char *argv[])
return CommandResult::OK;
}
+/**
+ * Parse a string in the form "START:END", both being (optional)
+ * fractional non-negative time offsets in seconds. Returns both in
+ * integer milliseconds. Omitted values are zero.
+ */
+static bool
+parse_time_range(const char *p, SongTime &start_r, SongTime &end_r)
+{
+ char *endptr;
+
+ const float start = ParseFloat(p, &endptr);
+ if (*endptr != ':' || start < 0)
+ return false;
+
+ start_r = endptr > p
+ ? SongTime::FromS(start)
+ : SongTime::zero();
+
+ p = endptr + 1;
+
+ const float end = ParseFloat(p, &endptr);
+ if (*endptr != 0 || end < 0)
+ return false;
+
+ end_r = endptr > p
+ ? SongTime::FromS(end)
+ : SongTime::zero();
+
+ return end_r.IsZero() || end_r > start_r;
+}
+
+CommandResult
+handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ unsigned id;
+ if (!check_unsigned(client, &id, argv[1]))
+ return CommandResult::ERROR;
+
+ SongTime start, end;
+ if (!parse_time_range(argv[2], start, end)) {
+ command_error(client, ACK_ERROR_ARG, "Bad range");
+ return CommandResult::ERROR;
+ }
+
+ Error error;
+ if (!client.partition.playlist.SetSongIdRange(client.partition.pc,
+ id, start, end,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
CommandResult
-handle_delete(Client &client, gcc_unused int argc, char *argv[])
+handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned start, end;
@@ -148,7 +194,7 @@ handle_delete(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_deleteid(Client &client, gcc_unused int argc, char *argv[])
+handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned id;
@@ -161,7 +207,7 @@ handle_deleteid(Client &client, gcc_unused int argc, char *argv[])
CommandResult
handle_playlist(Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
playlist_print_uris(client, client.playlist);
return CommandResult::OK;
@@ -169,7 +215,7 @@ handle_playlist(Client &client,
CommandResult
handle_shuffle(gcc_unused Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
unsigned start = 0, end = client.playlist.queue.GetLength();
if (argc == 2 && !check_range(client, &start, &end, argv[1]))
@@ -181,14 +227,14 @@ handle_shuffle(gcc_unused Client &client,
CommandResult
handle_clear(gcc_unused Client &client,
- gcc_unused int argc, gcc_unused char *argv[])
+ gcc_unused unsigned argc, gcc_unused char *argv[])
{
client.partition.ClearQueue();
return CommandResult::OK;
}
CommandResult
-handle_plchanges(Client &client, gcc_unused int argc, char *argv[])
+handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[])
{
uint32_t version;
@@ -200,7 +246,7 @@ handle_plchanges(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[])
+handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[])
{
uint32_t version;
@@ -212,7 +258,7 @@ handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_playlistinfo(Client &client, int argc, char *argv[])
+handle_playlistinfo(Client &client, unsigned argc, char *argv[])
{
unsigned start = 0, end = std::numeric_limits<unsigned>::max();
bool ret;
@@ -229,7 +275,7 @@ handle_playlistinfo(Client &client, int argc, char *argv[])
}
CommandResult
-handle_playlistid(Client &client, int argc, char *argv[])
+handle_playlistid(Client &client, unsigned argc, char *argv[])
{
if (argc >= 2) {
unsigned id;
@@ -249,11 +295,13 @@ handle_playlistid(Client &client, int argc, char *argv[])
}
static CommandResult
-handle_playlist_match(Client &client, int argc, char *argv[],
+handle_playlist_match(Client &client, unsigned argc, char *argv[],
bool fold_case)
{
+ ConstBuffer<const char *> args(argv + 1, argc - 1);
+
SongFilter filter;
- if (!filter.Parse(argc - 1, argv + 1, fold_case)) {
+ if (!filter.Parse(args, fold_case)) {
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return CommandResult::ERROR;
}
@@ -263,19 +311,19 @@ handle_playlist_match(Client &client, int argc, char *argv[],
}
CommandResult
-handle_playlistfind(Client &client, int argc, char *argv[])
+handle_playlistfind(Client &client, unsigned argc, char *argv[])
{
return handle_playlist_match(client, argc, argv, false);
}
CommandResult
-handle_playlistsearch(Client &client, int argc, char *argv[])
+handle_playlistsearch(Client &client, unsigned argc, char *argv[])
{
return handle_playlist_match(client, argc, argv, true);
}
CommandResult
-handle_prio(Client &client, int argc, char *argv[])
+handle_prio(Client &client, unsigned argc, char *argv[])
{
unsigned priority;
@@ -288,7 +336,7 @@ handle_prio(Client &client, int argc, char *argv[])
return CommandResult::ERROR;
}
- for (int i = 2; i < argc; ++i) {
+ for (unsigned i = 2; i < argc; ++i) {
unsigned start_position, end_position;
if (!check_range(client, &start_position, &end_position,
argv[i]))
@@ -306,7 +354,7 @@ handle_prio(Client &client, int argc, char *argv[])
}
CommandResult
-handle_prioid(Client &client, int argc, char *argv[])
+handle_prioid(Client &client, unsigned argc, char *argv[])
{
unsigned priority;
@@ -319,7 +367,7 @@ handle_prioid(Client &client, int argc, char *argv[])
return CommandResult::ERROR;
}
- for (int i = 2; i < argc; ++i) {
+ for (unsigned i = 2; i < argc; ++i) {
unsigned song_id;
if (!check_unsigned(client, &song_id, argv[i]))
return CommandResult::ERROR;
@@ -334,7 +382,7 @@ handle_prioid(Client &client, int argc, char *argv[])
}
CommandResult
-handle_move(Client &client, gcc_unused int argc, char *argv[])
+handle_move(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned start, end;
int to;
@@ -350,7 +398,7 @@ handle_move(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_moveid(Client &client, gcc_unused int argc, char *argv[])
+handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned id;
int to;
@@ -364,7 +412,7 @@ handle_moveid(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_swap(Client &client, gcc_unused int argc, char *argv[])
+handle_swap(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned song1, song2;
@@ -379,7 +427,7 @@ handle_swap(Client &client, gcc_unused int argc, char *argv[])
}
CommandResult
-handle_swapid(Client &client, gcc_unused int argc, char *argv[])
+handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned id1, id2;
diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx
index 90d744447..f98f7bad2 100644
--- a/src/command/QueueCommands.hxx
+++ b/src/command/QueueCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,60 +25,63 @@
class Client;
CommandResult
-handle_add(Client &client, int argc, char *argv[]);
+handle_add(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_addid(Client &client, int argc, char *argv[]);
+handle_addid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_delete(Client &client, int argc, char *argv[]);
+handle_rangeid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_deleteid(Client &client, int argc, char *argv[]);
+handle_delete(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlist(Client &client, int argc, char *argv[]);
+handle_deleteid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_shuffle(Client &client, int argc, char *argv[]);
+handle_playlist(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_clear(Client &client, int argc, char *argv[]);
+handle_shuffle(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_plchanges(Client &client, int argc, char *argv[]);
+handle_clear(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_plchangesposid(Client &client, int argc, char *argv[]);
+handle_plchanges(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistinfo(Client &client, int argc, char *argv[]);
+handle_plchangesposid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistid(Client &client, int argc, char *argv[]);
+handle_playlistinfo(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistfind(Client &client, int argc, char *argv[]);
+handle_playlistid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistsearch(Client &client, int argc, char *argv[]);
+handle_playlistfind(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_prio(Client &client, int argc, char *argv[]);
+handle_playlistsearch(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_prioid(Client &client, int argc, char *argv[]);
+handle_prio(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_move(Client &client, int argc, char *argv[]);
+handle_prioid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_moveid(Client &client, int argc, char *argv[]);
+handle_move(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_swap(Client &client, int argc, char *argv[]);
+handle_moveid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_swapid(Client &client, int argc, char *argv[]);
+handle_swap(Client &client, unsigned argc, char *argv[]);
+
+CommandResult
+handle_swapid(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx
index b65e6f607..37506d51b 100644
--- a/src/command/StickerCommands.cxx
+++ b/src/command/StickerCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,19 +20,18 @@
#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 "db/Interface.hxx"
+#include "db/DatabaseGlue.hxx"
+#include "sticker/SongSticker.hxx"
+#include "sticker/StickerPrint.hxx"
+#include "sticker/StickerDatabase.hxx"
#include "CommandError.hxx"
#include "protocol/Result.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
#include "util/Error.hxx"
-#include <glib.h>
-
#include <string.h>
struct sticker_song_find_data {
@@ -41,7 +40,7 @@ struct sticker_song_find_data {
};
static void
-sticker_song_find_print_cb(Song &song, const char *value,
+sticker_song_find_print_cb(const LightSong &song, const char *value,
void *user_data)
{
struct sticker_song_find_data *data =
@@ -52,20 +51,20 @@ sticker_song_find_print_cb(Song &song, const char *value,
}
static CommandResult
-handle_sticker_song(Client &client, int argc, char *argv[])
+handle_sticker_song(Client &client, unsigned argc, char *argv[])
{
Error error;
- const Database *db = GetDatabase(error);
+ const Database *db = client.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);
+ const LightSong *song = db->GetSong(argv[3], error);
if (song == nullptr)
return print_error(client, error);
- const auto value = sticker_song_get_value(song, argv[4]);
+ const auto value = sticker_song_get_value(*song, argv[4]);
db->ReturnSong(song);
if (value.empty()) {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -78,11 +77,11 @@ handle_sticker_song(Client &client, int argc, char *argv[])
return CommandResult::OK;
/* list song song_id */
} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *song = db->GetSong(argv[3], error);
if (song == nullptr)
return print_error(client, error);
- sticker *sticker = sticker_song_get(song);
+ sticker *sticker = sticker_song_get(*song);
db->ReturnSong(song);
if (sticker) {
sticker_print(client, *sticker);
@@ -92,11 +91,11 @@ handle_sticker_song(Client &client, int argc, char *argv[])
return CommandResult::OK;
/* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *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]);
+ bool ret = sticker_song_set_value(*song, argv[4], argv[5]);
db->ReturnSong(song);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
@@ -108,13 +107,13 @@ handle_sticker_song(Client &client, int argc, char *argv[])
/* delete song song_id [key] */
} else if ((argc == 4 || argc == 5) &&
strcmp(argv[1], "delete") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *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]);
+ ? sticker_song_delete(*song)
+ : sticker_song_delete_value(*song, argv[4]);
db->ReturnSong(song);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
@@ -126,24 +125,17 @@ handle_sticker_song(Client &client, int argc, char *argv[])
/* find song dir key */
} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
/* "sticker find song a/directory name" */
+
+ const char *const base_uri = argv[3];
+
bool success;
struct sticker_song_find_data data = {
client,
argv[4],
};
- db_lock();
- Directory *directory = db_get_directory(argv[3]);
- if (directory == nullptr) {
- db_unlock();
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such directory");
- return CommandResult::ERROR;
- }
-
- success = sticker_song_find(*directory, data.name,
+ success = sticker_song_find(*db, base_uri, data.name,
sticker_song_find_print_cb, &data);
- db_unlock();
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"failed to set search sticker database");
@@ -158,7 +150,7 @@ handle_sticker_song(Client &client, int argc, char *argv[])
}
CommandResult
-handle_sticker(Client &client, int argc, char *argv[])
+handle_sticker(Client &client, unsigned argc, char *argv[])
{
assert(argc >= 4);
diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx
index 9e4380f37..cf46cd034 100644
--- a/src/command/StickerCommands.hxx
+++ b/src/command/StickerCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,6 @@
class Client;
CommandResult
-handle_sticker(Client &client, int argc, char *argv[]);
+handle_sticker(Client &client, unsigned argc, char *argv[]);
#endif
diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx
new file mode 100644
index 000000000..ee51c573e
--- /dev/null
+++ b/src/command/StorageCommands.cxx
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define __STDC_FORMAT_MACROS /* for PRIu64 */
+
+#include "config.h"
+#include "StorageCommands.hxx"
+#include "CommandError.hxx"
+#include "protocol/Result.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "fs/Traits.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "storage/Registry.hxx"
+#include "storage/CompositeStorage.hxx"
+#include "storage/FileInfo.hxx"
+#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
+#include "db/update/Service.hxx"
+#include "TimePrint.hxx"
+#include "IOThread.hxx"
+#include "Idle.hxx"
+
+#include <inttypes.h> /* for PRIu64 */
+
+gcc_pure
+static bool
+skip_path(const char *name_utf8)
+{
+ return strchr(name_utf8, '\n') != nullptr;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+/* PRIu64 causes bogus compiler warning */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+#endif
+
+static bool
+handle_listfiles_storage(Client &client, StorageDirectoryReader &reader,
+ Error &error)
+{
+ const char *name_utf8;
+ while ((name_utf8 = reader.Read()) != nullptr) {
+ if (skip_path(name_utf8))
+ continue;
+
+ FileInfo info;
+ if (!reader.GetInfo(false, info, error))
+ continue;
+
+ switch (info.type) {
+ case FileInfo::Type::OTHER:
+ /* ignore */
+ continue;
+
+ case FileInfo::Type::REGULAR:
+ client_printf(client, "file: %s\n"
+ "size: %" PRIu64 "\n",
+ name_utf8,
+ info.size);
+ break;
+
+ case FileInfo::Type::DIRECTORY:
+ client_printf(client, "directory: %s\n", name_utf8);
+ break;
+ }
+
+ if (info.mtime != 0)
+ time_print(client, "Last-Modified", info.mtime);
+ }
+
+ return true;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
+
+static bool
+handle_listfiles_storage(Client &client, Storage &storage, const char *uri,
+ Error &error)
+{
+ auto reader = storage.OpenDirectory(uri, error);
+ if (reader == nullptr)
+ return false;
+
+ bool success = handle_listfiles_storage(client, *reader, error);
+ delete reader;
+ return success;
+}
+
+CommandResult
+handle_listfiles_storage(Client &client, Storage &storage, const char *uri)
+{
+ Error error;
+ if (!handle_listfiles_storage(client, storage, uri, error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_listfiles_storage(Client &client, const char *uri)
+{
+ Error error;
+ Storage *storage = CreateStorageURI(io_thread_get(), uri, error);
+ if (storage == nullptr) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized storage URI");
+ return CommandResult::ERROR;
+ }
+
+ bool success = handle_listfiles_storage(client, *storage, "", error);
+ delete storage;
+ if (!success)
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+static void
+print_storage_uri(Client &client, const Storage &storage)
+{
+ std::string uri = storage.MapUTF8("");
+ if (uri.empty())
+ return;
+
+ if (PathTraitsFS::IsAbsolute(uri.c_str())) {
+ /* storage points to local directory */
+
+ if (!client.IsLocal())
+ /* only "local" clients may see local paths
+ (same policy as with the "config"
+ command) */
+ return;
+ } else {
+ /* hide username/passwords from client */
+
+ std::string allocated = uri_remove_auth(uri.c_str());
+ if (!allocated.empty())
+ uri = std::move(allocated);
+ }
+
+ client_printf(client, "storage: %s\n", uri.c_str());
+}
+
+CommandResult
+handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[])
+{
+ Storage *_composite = client.partition.instance.storage;
+ if (_composite == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ CompositeStorage &composite = *(CompositeStorage *)_composite;
+
+ const auto visitor = [&client](const char *mount_uri,
+ const Storage &storage){
+ client_printf(client, "mount: %s\n", mount_uri);
+ print_storage_uri(client, storage);
+ };
+
+ composite.VisitMounts(visitor);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ Storage *_composite = client.partition.instance.storage;
+ if (_composite == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ CompositeStorage &composite = *(CompositeStorage *)_composite;
+
+ const char *const local_uri = argv[1];
+ const char *const remote_uri = argv[2];
+
+ if (*local_uri == 0) {
+ command_error(client, ACK_ERROR_ARG, "Bad mount point");
+ return CommandResult::ERROR;
+ }
+
+ if (strchr(local_uri, '/') != nullptr) {
+ /* allow only top-level mounts for now */
+ /* TODO: eliminate this limitation after ensuring that
+ UpdateQueue::Erase() really gets called for every
+ unmount, and no Directory disappears recursively
+ during database update */
+ command_error(client, ACK_ERROR_ARG, "Bad mount point");
+ return CommandResult::ERROR;
+ }
+
+ Error error;
+ Storage *storage = CreateStorageURI(io_thread_get(), remote_uri,
+ error);
+ if (storage == nullptr) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized storage URI");
+ return CommandResult::ERROR;
+ }
+
+ composite.Mount(local_uri, storage);
+ idle_add(IDLE_MOUNT);
+
+#ifdef ENABLE_DATABASE
+ Database *_db = client.partition.instance.database;
+ if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) {
+ SimpleDatabase &db = *(SimpleDatabase *)_db;
+
+ if (!db.Mount(local_uri, remote_uri, error)) {
+ composite.Unmount(local_uri);
+ return print_error(client, error);
+ }
+
+ // TODO: call Instance::OnDatabaseModified()?
+ // TODO: trigger database update?
+ idle_add(IDLE_DATABASE);
+ }
+#endif
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ Storage *_composite = client.partition.instance.storage;
+ if (_composite == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ CompositeStorage &composite = *(CompositeStorage *)_composite;
+
+ const char *const local_uri = argv[1];
+
+ if (*local_uri == 0) {
+ command_error(client, ACK_ERROR_ARG, "Bad mount point");
+ return CommandResult::ERROR;
+ }
+
+#ifdef ENABLE_DATABASE
+ if (client.partition.instance.update != nullptr)
+ /* ensure that no database update will attempt to work
+ with the database/storage instances we're about to
+ destroy here */
+ client.partition.instance.update->CancelMount(local_uri);
+
+ Database *_db = client.partition.instance.database;
+ if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) {
+ SimpleDatabase &db = *(SimpleDatabase *)_db;
+
+ if (db.Unmount(local_uri))
+ // TODO: call Instance::OnDatabaseModified()?
+ idle_add(IDLE_DATABASE);
+ }
+#endif
+
+ if (!composite.Unmount(local_uri)) {
+ command_error(client, ACK_ERROR_ARG, "Not a mount point");
+ return CommandResult::ERROR;
+ }
+
+ idle_add(IDLE_MOUNT);
+
+ return CommandResult::OK;
+}
diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx
new file mode 100644
index 000000000..a3636d54a
--- /dev/null
+++ b/src/command/StorageCommands.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_COMMANDS_HXX
+#define MPD_STORAGE_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+
+class Client;
+class Storage;
+
+CommandResult
+handle_listfiles_storage(Client &client, Storage &storage, const char *uri);
+
+CommandResult
+handle_listfiles_storage(Client &client, const char *uri);
+
+CommandResult
+handle_listmounts(Client &client, unsigned argc, char *argv[]);
+
+CommandResult
+handle_mount(Client &client, unsigned argc, char *argv[]);
+
+CommandResult
+handle_unmount(Client &client, unsigned argc, char *argv[]);
+
+#endif
diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx
new file mode 100644
index 000000000..2d537671c
--- /dev/null
+++ b/src/command/TagCommands.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TagCommands.hxx"
+#include "CommandError.hxx"
+#include "client/Client.hxx"
+#include "protocol/ArgParser.hxx"
+#include "protocol/Result.hxx"
+#include "tag/Tag.hxx"
+#include "Partition.hxx"
+
+CommandResult
+handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[1]))
+ return CommandResult::ERROR;
+
+ const char *const tag_name = argv[2];
+ const TagType tag_type = tag_name_parse_i(tag_name);
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", tag_name);
+ return CommandResult::ERROR;
+ }
+
+ const char *const value = argv[3];
+
+ Error error;
+ if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_cleartagid(Client &client, unsigned argc, char *argv[])
+{
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[1]))
+ return CommandResult::ERROR;
+
+ TagType tag_type = TAG_NUM_OF_ITEM_TYPES;
+ if (argc >= 3) {
+ const char *const tag_name = argv[2];
+ tag_type = tag_name_parse_i(tag_name);
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", tag_name);
+ return CommandResult::ERROR;
+ }
+ }
+
+ Error error;
+ if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx
new file mode 100644
index 000000000..748838e68
--- /dev/null
+++ b/src/command/TagCommands.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_COMMANDS_HXX
+#define MPD_TAG_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+
+class Client;
+
+CommandResult
+handle_addtagid(Client &client, unsigned argc, char *argv[]);
+
+CommandResult
+handle_cleartagid(Client &client, unsigned argc, char *argv[]);
+
+#endif
diff --git a/src/config/ConfigData.cxx b/src/config/ConfigData.cxx
new file mode 100644
index 000000000..70e1e55ed
--- /dev/null
+++ b/src/config/ConfigData.cxx
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConfigData.hxx"
+#include "ConfigParser.hxx"
+#include "ConfigPath.hxx"
+#include "util/Error.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "system/FatalError.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+int
+block_param::GetIntValue() const
+{
+ char *endptr;
+ long value2 = strtol(value.c_str(), &endptr, 0);
+ if (*endptr != 0)
+ FormatFatalError("Not a valid number in line %i", line);
+
+ return value2;
+}
+
+unsigned
+block_param::GetUnsignedValue() const
+{
+ char *endptr;
+ unsigned long value2 = strtoul(value.c_str(), &endptr, 0);
+ if (*endptr != 0)
+ FormatFatalError("Not a valid number in line %i", line);
+
+ return (unsigned)value2;
+}
+
+bool
+block_param::GetBoolValue() const
+{
+ bool value2;
+ if (!get_bool(value.c_str(), &value2))
+ FormatFatalError("%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(_value), line(_line), used(false) {}
+
+config_param::~config_param()
+{
+ delete next;
+}
+
+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();
+}
+
+AllocatedPath
+config_param::GetBlockPath(const char *name, const char *default_value,
+ Error &error) const
+{
+ assert(!error.IsDefined());
+
+ int line2 = line;
+ const char *s;
+
+ const block_param *bp = GetBlockParam(name);
+ if (bp != nullptr) {
+ line2 = bp->line;
+ s = bp->value.c_str();
+ } else {
+ if (default_value == nullptr)
+ return AllocatedPath::Null();
+
+ s = default_value;
+ }
+
+ AllocatedPath path = ParsePath(s, error);
+ if (gcc_unlikely(path.IsNull()))
+ error.FormatPrefix("Invalid path in \"%s\" at line %i: ",
+ name, line2);
+
+ return path;
+}
+
+AllocatedPath
+config_param::GetBlockPath(const char *name, Error &error) const
+{
+ return GetBlockPath(name, nullptr, error);
+}
+
+int
+config_param::GetBlockValue(const char *name, int default_value) const
+{
+ const block_param *bp = GetBlockParam(name);
+ if (bp == nullptr)
+ return default_value;
+
+ return bp->GetIntValue();
+}
+
+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/config/ConfigData.hxx b/src/config/ConfigData.hxx
new file mode 100644
index 000000000..e42d674ba
--- /dev/null
+++ b/src/config/ConfigData.hxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_DATA_HXX
+#define MPD_CONFIG_DATA_HXX
+
+#include "ConfigOption.hxx"
+#include "Compiler.h"
+
+#include <string>
+#include <array>
+#include <vector>
+
+class AllocatedPath;
+class Error;
+
+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
+ int GetIntValue() const;
+
+ 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;
+
+ std::string 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), 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;
+
+ /**
+ * Same as config_dup_path(), but looks up the setting in the
+ * specified block.
+ */
+ AllocatedPath GetBlockPath(const char *name, const char *default_value,
+ Error &error) const;
+
+ AllocatedPath GetBlockPath(const char *name, Error &error) const;
+
+ gcc_pure
+ int GetBlockValue(const char *name, int default_value) 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/config/ConfigDefaults.hxx b/src/config/ConfigDefaults.hxx
new file mode 100644
index 000000000..c50f28c91
--- /dev/null
+++ b/src/config/ConfigDefaults.hxx
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_DEFAULTS_HXX
+#define MPD_CONFIG_DEFAULTS_HXX
+
+static constexpr unsigned DEFAULT_PLAYLIST_MAX_LENGTH = 16 * 1024;
+static constexpr bool DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS = false;
+
+#endif
diff --git a/src/config/ConfigError.cxx b/src/config/ConfigError.cxx
new file mode 100644
index 000000000..70aff7175
--- /dev/null
+++ b/src/config/ConfigError.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ConfigError.hxx"
+#include "util/Domain.hxx"
+
+const Domain config_domain("config");
diff --git a/src/config/ConfigError.hxx b/src/config/ConfigError.hxx
new file mode 100644
index 000000000..cbfa79df3
--- /dev/null
+++ b/src/config/ConfigError.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_ERROR_HXX
+#define MPD_CONFIG_ERROR_HXX
+
+extern const class Domain config_domain;
+
+#endif
diff --git a/src/config/ConfigFile.cxx b/src/config/ConfigFile.cxx
new file mode 100644
index 000000000..1329c4cd4
--- /dev/null
+++ b/src/config/ConfigFile.cxx
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConfigFile.hxx"
+#include "ConfigData.hxx"
+#include "ConfigTemplates.hxx"
+#include "util/Tokenizer.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "fs/Limits.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <stdio.h>
+
+#define MAX_STRING_SIZE MPD_PATH_MAX+80
+
+#define CONF_COMMENT '#'
+
+static constexpr Domain config_file_domain("config_file");
+
+static bool
+config_read_name_value(struct config_param *param, char *input, unsigned line,
+ Error &error)
+{
+ Tokenizer tokenizer(input);
+
+ const char *name = tokenizer.NextWord(error);
+ if (name == nullptr) {
+ assert(!tokenizer.IsEnd());
+ return false;
+ }
+
+ const char *value = tokenizer.NextString(error);
+ if (value == nullptr) {
+ if (tokenizer.IsEnd()) {
+ error.Set(config_file_domain, "Value missing");
+ } else {
+ assert(error.IsDefined());
+ }
+
+ return false;
+ }
+
+ if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) {
+ error.Set(config_file_domain, "Unknown tokens after value");
+ return false;
+ }
+
+ const struct block_param *bp = param->GetBlockParam(name);
+ if (bp != nullptr) {
+ error.Format(config_file_domain,
+ "\"%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, Error &error)
+{
+ struct config_param *ret = new config_param(*count);
+
+ while (true) {
+ char *line;
+
+ line = fgets(string, MAX_STRING_SIZE, fp);
+ if (line == nullptr) {
+ delete ret;
+ error.Set(config_file_domain,
+ "Expected '}' before end-of-file");
+ return nullptr;
+ }
+
+ (*count)++;
+ line = StripLeft(line);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
+
+ if (*line == '}') {
+ /* end of this block; return from the function
+ (and from this "while" loop) */
+
+ line = StripLeft(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ delete ret;
+ error.Format(config_file_domain,
+ "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;
+ error.FormatPrefix("line %i: ", *count);
+ return nullptr;
+ }
+ }
+}
+
+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, Error &error)
+{
+ 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;
+
+ count++;
+
+ line = StripLeft(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 == nullptr) {
+ assert(!tokenizer.IsEnd());
+ error.FormatPrefix("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) {
+ error.Format(config_file_domain,
+ "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;
+ error.Format(config_file_domain,
+ "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() != '{') {
+ error.Format(config_file_domain,
+ "line %i: '{' expected", count);
+ return false;
+ }
+
+ line = StripLeft(tokenizer.Rest() + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ error.Format(config_file_domain,
+ "line %i: Unknown tokens after '{'",
+ count);
+ return false;
+ }
+
+ param = config_read_block(fp, &count, string, error);
+ if (param == nullptr) {
+ return false;
+ }
+ } else {
+ /* a string value */
+
+ value = tokenizer.NextString(error);
+ if (value == nullptr) {
+ if (tokenizer.IsEnd())
+ error.Format(config_file_domain,
+ "line %i: Value missing",
+ count);
+ else
+ error.FormatPrefix("line %i: ", count);
+
+ return false;
+ }
+
+ if (!tokenizer.IsEnd() &&
+ tokenizer.CurrentChar() != CONF_COMMENT) {
+ error.Format(config_file_domain,
+ "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, Path path, Error &error)
+{
+ assert(!path.IsNull());
+ const std::string path_utf8 = path.ToUTF8();
+
+ FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
+
+ FILE *fp = FOpen(path, FOpenMode::ReadText);
+ if (fp == nullptr) {
+ error.FormatErrno("Failed to open %s", path_utf8.c_str());
+ return false;
+ }
+
+ bool result = ReadConfigFile(config_data, fp, error);
+ fclose(fp);
+ return result;
+}
diff --git a/src/config/ConfigFile.hxx b/src/config/ConfigFile.hxx
new file mode 100644
index 000000000..b87182c6a
--- /dev/null
+++ b/src/config/ConfigFile.hxx
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_FILE_HXX
+#define MPD_CONFIG_FILE_HXX
+
+class Error;
+class Path;
+struct ConfigData;
+
+bool
+ReadConfigFile(ConfigData &data, Path path, Error &error);
+
+#endif
diff --git a/src/config/ConfigGlobal.cxx b/src/config/ConfigGlobal.cxx
new file mode 100644
index 000000000..9bc83398c
--- /dev/null
+++ b/src/config/ConfigGlobal.cxx
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConfigGlobal.hxx"
+#include "ConfigParser.hxx"
+#include "ConfigData.hxx"
+#include "ConfigFile.hxx"
+#include "ConfigPath.hxx"
+#include "ConfigError.hxx"
+#include "fs/Path.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/Error.hxx"
+#include "system/FatalError.hxx"
+#include "Log.hxx"
+
+#include <stdlib.h>
+
+static ConfigData config_data;
+
+void config_global_finish(void)
+{
+ for (auto i : config_data.params)
+ delete i;
+}
+
+void config_global_init(void)
+{
+}
+
+bool
+ReadConfigFile(Path path, Error &error)
+{
+ return ReadConfigFile(config_data, path, error);
+}
+
+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)
+ FormatWarning(config_domain,
+ "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 config_param *
+config_get_param(ConfigOption option)
+{
+ config_param *param = config_data.params[unsigned(option)];
+ if (param != nullptr)
+ param->used = true;
+ return param;
+}
+
+const config_param *
+config_find_block(ConfigOption option, const char *key, const char *value)
+{
+ for (const config_param *param = config_get_param(option);
+ param != nullptr; param = param->next) {
+ const char *value2 = param->GetBlockValue(key);
+ if (value2 == nullptr)
+ FormatFatalError("block without '%s' name in line %d",
+ key, param->line);
+
+ if (strcmp(value2, value) == 0)
+ return param;
+ }
+
+ return nullptr;
+}
+
+const char *
+config_get_string(ConfigOption option, const char *default_value)
+{
+ const struct config_param *param = config_get_param(option);
+
+ if (param == nullptr)
+ return default_value;
+
+ return param->value.c_str();
+}
+
+AllocatedPath
+config_get_path(ConfigOption option, Error &error)
+{
+ const struct config_param *param = config_get_param(option);
+ if (param == nullptr)
+ return AllocatedPath::Null();
+
+ return config_parse_path(param, error);
+}
+
+AllocatedPath
+config_parse_path(const struct config_param *param, Error & error)
+{
+ AllocatedPath path = ParsePath(param->value.c_str(), error);
+ if (gcc_unlikely(path.IsNull()))
+ error.FormatPrefix("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 == nullptr)
+ return default_value;
+
+ value = strtol(param->value.c_str(), &endptr, 0);
+ if (*endptr != 0 || value < 0)
+ FormatFatalError("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 == nullptr)
+ return default_value;
+
+ value = strtol(param->value.c_str(), &endptr, 0);
+ if (*endptr != 0)
+ FormatFatalError("Not a valid number in line %i", param->line);
+
+ if (value <= 0)
+ FormatFatalError("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 == nullptr)
+ return default_value;
+
+ success = get_bool(param->value.c_str(), &value);
+ if (!success)
+ FormatFatalError("Expected boolean value (yes, true, 1) or "
+ "(no, false, 0) on line %i\n",
+ param->line);
+
+ return value;
+}
diff --git a/src/config/ConfigGlobal.hxx b/src/config/ConfigGlobal.hxx
new file mode 100644
index 000000000..831418d03
--- /dev/null
+++ b/src/config/ConfigGlobal.hxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_GLOBAL_HXX
+#define MPD_CONFIG_GLOBAL_HXX
+
+#include "ConfigOption.hxx"
+#include "Compiler.h"
+
+class Error;
+class Path;
+class AllocatedPath;
+struct config_param;
+
+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
+ReadConfigFile(Path path, Error &error);
+
+gcc_pure
+const config_param *
+config_get_param(enum ConfigOption option);
+
+/**
+ * Find a block with a matching attribute.
+ *
+ * @param option the blocks to search
+ * @param key the attribute name
+ * @param value the expected attribute value
+ */
+gcc_pure
+const config_param *
+config_find_block(ConfigOption option, const char *key, const char *value);
+
+/* 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 AllocatedPath::Null() if the value is not present. If the path
+ * could not be parsed, returns AllocatedPath::Null() and sets the error.
+ */
+AllocatedPath
+config_get_path(enum ConfigOption option, Error &error);
+
+/**
+ * Parse a configuration parameter as a path.
+ * If there is a tilde prefix, it is expanded. If the path could
+ * not be parsed, returns AllocatedPath::Null() and sets the error.
+ */
+AllocatedPath
+config_parse_path(const struct config_param *param, Error & 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);
+
+#endif
diff --git a/src/config/ConfigOption.hxx b/src/config/ConfigOption.hxx
new file mode 100644
index 000000000..8eb4c7eaf
--- /dev/null
+++ b/src/config/ConfigOption.hxx
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_OPTION_HXX
+#define MPD_CONFIG_OPTION_HXX
+
+#include "Compiler.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_STATE_FILE_INTERVAL,
+ 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_NEIGHBORS,
+ CONF_MAX
+};
+
+/**
+ * @return #CONF_MAX if not found
+ */
+gcc_pure
+enum ConfigOption
+ParseConfigOptionName(const char *name);
+
+#endif
diff --git a/src/config/ConfigParser.cxx b/src/config/ConfigParser.cxx
new file mode 100644
index 000000000..3535c9a13
--- /dev/null
+++ b/src/config/ConfigParser.cxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "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/config/ConfigParser.hxx b/src/config/ConfigParser.hxx
new file mode 100644
index 000000000..06151b0bd
--- /dev/null
+++ b/src/config/ConfigParser.hxx
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_PARSER_HXX
+#define MPD_CONFIG_PARSER_HXX
+
+bool
+get_bool(const char *value, bool *value_r);
+
+#endif
diff --git a/src/config/ConfigPath.cxx b/src/config/ConfigPath.cxx
new file mode 100644
index 000000000..a3b3f83a5
--- /dev/null
+++ b/src/config/ConfigPath.cxx
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConfigPath.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/Traits.hxx"
+#include "fs/Domain.hxx"
+#include "fs/StandardDirectory.hxx"
+#include "util/Error.hxx"
+#include "ConfigGlobal.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#ifndef WIN32
+#include <pwd.h>
+
+/**
+ * Determine a given user's home directory.
+ */
+static AllocatedPath
+GetHome(const char *user, Error &error)
+{
+ AllocatedPath result = GetHomeDir(user);
+ if (result.IsNull()) {
+ error.Format(path_domain,
+ "no such user: %s", user);
+ return AllocatedPath::Null();
+ }
+
+ return result;
+}
+
+/**
+ * Determine the current user's home directory.
+ */
+static AllocatedPath
+GetHome(Error &error)
+{
+ AllocatedPath result = GetHomeDir();
+ if (result.IsNull()) {
+ error.Set(path_domain,
+ "problems getting home for current user");
+ return AllocatedPath::Null();
+ }
+
+ return result;
+}
+
+/**
+ * Determine the configured user's home directory.
+ */
+static AllocatedPath
+GetConfiguredHome(Error &error)
+{
+ const char *user = config_get_string(CONF_USER, nullptr);
+ return user != nullptr
+ ? GetHome(user, error)
+ : GetHome(error);
+}
+
+#endif
+
+AllocatedPath
+ParsePath(const char *path, Error &error)
+{
+ assert(path != nullptr);
+
+#ifndef WIN32
+ if (path[0] == '~') {
+ ++path;
+
+ if (*path == '\0')
+ return GetConfiguredHome(error);
+
+ AllocatedPath home = AllocatedPath::Null();
+
+ if (*path == '/') {
+ home = GetConfiguredHome(error);
+
+ ++path;
+ } else {
+ const char *slash = strchr(path, '/');
+ const char *end = slash == nullptr
+ ? path + strlen(path)
+ : slash;
+ const std::string user(path, end);
+ home = GetHome(user.c_str(), error);
+
+ if (slash == nullptr)
+ return home;
+
+ path = slash + 1;
+ }
+
+ if (home.IsNull())
+ return AllocatedPath::Null();
+
+ AllocatedPath path2 = AllocatedPath::FromUTF8(path, error);
+ if (path2.IsNull())
+ return AllocatedPath::Null();
+
+ return AllocatedPath::Build(home, path2);
+ } else if (!PathTraitsUTF8::IsAbsolute(path)) {
+ error.Format(path_domain,
+ "not an absolute path: %s", path);
+ return AllocatedPath::Null();
+ } else {
+#endif
+ return AllocatedPath::FromUTF8(path, error);
+#ifndef WIN32
+ }
+#endif
+}
diff --git a/src/config/ConfigPath.hxx b/src/config/ConfigPath.hxx
new file mode 100644
index 000000000..a5518a497
--- /dev/null
+++ b/src/config/ConfigPath.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_PATH_HXX
+#define MPD_CONFIG_PATH_HXX
+
+class AllocatedPath;
+class Error;
+
+AllocatedPath
+ParsePath(const char *path, Error &error);
+
+#endif
diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx
new file mode 100644
index 000000000..58ee56425
--- /dev/null
+++ b/src/config/ConfigTemplates.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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 },
+ { "state_file_interval", 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 },
+ { "neighbors", true, 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/config/ConfigTemplates.hxx b/src/config/ConfigTemplates.hxx
new file mode 100644
index 000000000..90d098dc0
--- /dev/null
+++ b/src/config/ConfigTemplates.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIG_TEMPLATES_HXX
+#define MPD_CONFIG_TEMPLATES_HXX
+
+struct ConfigTemplate {
+ const char *const name;
+ const bool repeatable;
+ const bool block;
+};
+
+extern const ConfigTemplate config_templates[];
+
+#endif
diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx
deleted file mode 100644
index 60b33b6b4..000000000
--- a/src/cue/CueParser.cxx
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "CueParser.hxx"
-#include "util/StringUtil.hxx"
-#include "util/CharUtil.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-CueParser::CueParser()
- :state(HEADER), tag(new Tag()),
- current(nullptr),
- previous(nullptr),
- finished(nullptr),
- end(false) {}
-
-CueParser::~CueParser()
-{
- delete tag;
-
- 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(!IsWhitespaceNotNull(*p));
-
- const char *word = p;
- while (!IsWhitespaceOrNull(*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, TagType 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;
-
- TagType 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." */
-
- TagType 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;
- filename = 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.c_str());
- 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
deleted file mode 100644
index abcceaa2e..000000000
--- a/src/cue/CueParser.hxx
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CUE_PARSER_HXX
-#define MPD_CUE_PARSER_HXX
-
-#include "check.h"
-#include "Compiler.h"
-
-#include <string>
-
-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;
-
- std::string 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/db/Configured.cxx b/src/db/Configured.cxx
new file mode 100644
index 000000000..625300d75
--- /dev/null
+++ b/src/db/Configured.cxx
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Configured.hxx"
+#include "DatabaseGlue.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/StandardDirectory.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+Database *
+CreateConfiguredDatabase(EventLoop &loop, DatabaseListener &listener,
+ Error &error)
+{
+ const struct config_param *param = config_get_param(CONF_DATABASE);
+ const struct config_param *path = config_get_param(CONF_DB_FILE);
+
+ if (param != nullptr && path != nullptr) {
+ error.Format(config_domain,
+ "Found both 'database' (line %d) and 'db_file' (line %d) setting",
+ param->line, path->line);
+ return nullptr;
+ }
+
+ struct config_param *allocated = nullptr;
+
+ if (param == nullptr && path != nullptr) {
+ allocated = new config_param("database", path->line);
+ allocated->AddBlockParam("path", path->value.c_str(),
+ path->line);
+ param = allocated;
+ }
+
+ if (param == nullptr) {
+ /* if there is no override, use the cache directory */
+
+ const AllocatedPath cache_dir = GetUserCacheDir();
+ if (cache_dir.IsNull())
+ return nullptr;
+
+ const auto db_file = AllocatedPath::Build(cache_dir, "mpd.db");
+
+ allocated = new config_param("database");
+ allocated->AddBlockParam("path", db_file.c_str(), -1);
+ param = allocated;
+ }
+
+ Database *db = DatabaseGlobalInit(loop, listener, *param,
+ error);
+ delete allocated;
+ return db;
+}
diff --git a/src/db/Configured.hxx b/src/db/Configured.hxx
new file mode 100644
index 000000000..5d25b701c
--- /dev/null
+++ b/src/db/Configured.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_CONFIG_HXX
+#define MPD_DB_CONFIG_HXX
+
+#include "check.h"
+
+class EventLoop;
+class DatabaseListener;
+class Database;
+class Error;
+
+/**
+ * Read database configuration settings and create a #Database
+ * instance from it, but do not open it. Returns nullptr on error or
+ * if no database is configured (no #Error set in that case).
+ */
+Database *
+CreateConfiguredDatabase(EventLoop &loop, DatabaseListener &listener,
+ Error &error);
+
+#endif
diff --git a/src/db/Count.cxx b/src/db/Count.cxx
new file mode 100644
index 000000000..e5e244a6a
--- /dev/null
+++ b/src/db/Count.cxx
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Count.hxx"
+#include "Selection.hxx"
+#include "Interface.hxx"
+#include "client/Client.hxx"
+#include "LightSong.hxx"
+#include "tag/Tag.hxx"
+
+#include <functional>
+#include <map>
+
+struct SearchStats {
+ unsigned n_songs;
+ std::chrono::duration<std::uint64_t, SongTime::period> total_duration;
+
+ constexpr SearchStats()
+ :n_songs(0), total_duration(0) {}
+};
+
+class TagCountMap : public std::map<std::string, SearchStats> {
+};
+
+static void
+PrintSearchStats(Client &client, const SearchStats &stats)
+{
+ unsigned total_duration_s =
+ std::chrono::duration_cast<std::chrono::seconds>(stats.total_duration).count();
+
+ client_printf(client,
+ "songs: %u\n"
+ "playtime: %u\n",
+ stats.n_songs, total_duration_s);
+}
+
+static void
+Print(Client &client, TagType group, const TagCountMap &m)
+{
+ assert(unsigned(group) < TAG_NUM_OF_ITEM_TYPES);
+
+ for (const auto &i : m) {
+ client_printf(client, "%s: %s\n",
+ tag_item_names[group], i.first.c_str());
+ PrintSearchStats(client, i.second);
+ }
+}
+
+static bool
+stats_visitor_song(SearchStats &stats, const LightSong &song)
+{
+ stats.n_songs++;
+
+ const auto duration = song.GetDuration();
+ if (!duration.IsNegative())
+ stats.total_duration += duration;
+
+ return true;
+}
+
+static bool
+CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
+{
+ bool found = false;
+ for (const auto &item : tag) {
+ if (item.type == group) {
+ auto r = map.insert(std::make_pair(item.value,
+ SearchStats()));
+ SearchStats &s = r.first->second;
+ ++s.n_songs;
+ if (!tag.duration.IsNegative())
+ s.total_duration += tag.duration;
+
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+static bool
+GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
+{
+ assert(song.tag != nullptr);
+
+ const Tag &tag = *song.tag;
+ if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
+ /* fall back to "Artist" if no "AlbumArtist" was found */
+ CollectGroupCounts(map, TAG_ARTIST, tag);
+
+ return true;
+}
+
+bool
+PrintSongCount(Client &client, const char *name,
+ const SongFilter *filter,
+ TagType group,
+ Error &error)
+{
+ const Database *db = client.GetDatabase(error);
+ if (db == nullptr)
+ return false;
+
+ const DatabaseSelection selection(name, true, filter);
+
+ if (group == TAG_NUM_OF_ITEM_TYPES) {
+ /* no grouping */
+
+ SearchStats stats;
+
+ using namespace std::placeholders;
+ const auto f = std::bind(stats_visitor_song, std::ref(stats),
+ _1);
+ if (!db->Visit(selection, f, error))
+ return false;
+
+ PrintSearchStats(client, stats);
+ } else {
+ /* group by the specified tag: store counts in a
+ std::map */
+
+ TagCountMap map;
+
+ using namespace std::placeholders;
+ const auto f = std::bind(GroupCountVisitor, std::ref(map),
+ group, _1);
+ if (!db->Visit(selection, f, error))
+ return false;
+
+ Print(client, group, map);
+ }
+
+ return true;
+}
diff --git a/src/db/Count.hxx b/src/db/Count.hxx
new file mode 100644
index 000000000..d22a3210d
--- /dev/null
+++ b/src/db/Count.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_COUNT_HXX
+#define MPD_DB_COUNT_HXX
+
+#include "Compiler.h"
+
+#include <stdint.h>
+
+enum TagType : uint8_t;
+class Client;
+class SongFilter;
+class Error;
+
+gcc_nonnull(2)
+bool
+PrintSongCount(Client &client, const char *name,
+ const SongFilter *filter,
+ TagType group,
+ Error &error);
+
+#endif
diff --git a/src/db/DatabaseError.cxx b/src/db/DatabaseError.cxx
new file mode 100644
index 000000000..e0cbdd6a3
--- /dev/null
+++ b/src/db/DatabaseError.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabaseError.hxx"
+#include "util/Domain.hxx"
+
+const Domain db_domain("db");
diff --git a/src/db/DatabaseError.hxx b/src/db/DatabaseError.hxx
new file mode 100644
index 000000000..c71bbdfff
--- /dev/null
+++ b/src/db/DatabaseError.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_ERROR_HXX
+#define MPD_DB_ERROR_HXX
+
+class Domain;
+
+enum db_error {
+ /**
+ * The database is disabled, i.e. none is configured in this
+ * MPD instance.
+ */
+ DB_DISABLED,
+
+ DB_NOT_FOUND,
+
+ DB_CONFLICT,
+};
+
+extern const Domain db_domain;
+
+#endif
diff --git a/src/db/DatabaseGlue.cxx b/src/db/DatabaseGlue.cxx
new file mode 100644
index 000000000..ade5c95f3
--- /dev/null
+++ b/src/db/DatabaseGlue.cxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabaseGlue.hxx"
+#include "Registry.hxx"
+#include "DatabaseError.hxx"
+#include "util/Error.hxx"
+#include "config/ConfigData.hxx"
+#include "DatabasePlugin.hxx"
+
+#include <string.h>
+
+Database *
+DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error)
+{
+ const char *plugin_name =
+ param.GetBlockValue("plugin", "simple");
+
+ const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name);
+ if (plugin == nullptr) {
+ error.Format(db_domain,
+ "No such database plugin: %s", plugin_name);
+ return nullptr;
+ }
+
+ return plugin->create(loop, listener, param, error);
+}
diff --git a/src/db/DatabaseGlue.hxx b/src/db/DatabaseGlue.hxx
new file mode 100644
index 000000000..70b50def3
--- /dev/null
+++ b/src/db/DatabaseGlue.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_GLUE_HXX
+#define MPD_DATABASE_GLUE_HXX
+
+#include "Compiler.h"
+
+struct config_param;
+class EventLoop;
+class DatabaseListener;
+class Database;
+class Error;
+
+/**
+ * Initialize the database library.
+ *
+ * @param param the database configuration block
+ */
+Database *
+DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error);
+
+#endif
diff --git a/src/db/DatabaseListener.hxx b/src/db/DatabaseListener.hxx
new file mode 100644
index 000000000..8b410c2f5
--- /dev/null
+++ b/src/db/DatabaseListener.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_CLIENT_HXX
+#define MPD_DATABASE_CLIENT_HXX
+
+struct LightSong;
+
+/**
+ * An object that listens to events from the #Database.
+ *
+ * @see #Instance
+ */
+class DatabaseListener {
+public:
+ /**
+ * The database has been modified. This must be called in the
+ * thread that has created the #Database instance and that
+ * runs the #EventLoop.
+ */
+ virtual void OnDatabaseModified() = 0;
+
+ /**
+ * During database update, a song is about to be removed from
+ * the database because the file has disappeared.
+ */
+ virtual void OnDatabaseSongRemoved(const LightSong &song) = 0;
+};
+
+#endif
diff --git a/src/db/DatabaseLock.cxx b/src/db/DatabaseLock.cxx
new file mode 100644
index 000000000..c0b5e4844
--- /dev/null
+++ b/src/db/DatabaseLock.cxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabaseLock.hxx"
+
+Mutex db_mutex;
+
+#ifndef NDEBUG
+ThreadId db_mutex_holder;
+#endif
diff --git a/src/db/DatabaseLock.hxx b/src/db/DatabaseLock.hxx
new file mode 100644
index 000000000..9d0b0c152
--- /dev/null
+++ b/src/db/DatabaseLock.hxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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 "Compiler.h"
+
+#include <assert.h>
+
+extern Mutex db_mutex;
+
+#ifndef NDEBUG
+
+#include "thread/Id.hxx"
+
+extern ThreadId db_mutex_holder;
+
+/**
+ * Does the current thread hold the database lock?
+ */
+gcc_pure
+static inline bool
+holding_db_lock(void)
+{
+ return db_mutex_holder.IsInside();
+}
+
+#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.IsNull());
+#ifndef NDEBUG
+ db_mutex_holder = ThreadId::GetCurrent();
+#endif
+}
+
+/**
+ * Release the global database lock.
+ */
+static inline void
+db_unlock(void)
+{
+ assert(holding_db_lock());
+#ifndef NDEBUG
+ db_mutex_holder = ThreadId::Null();
+#endif
+
+ db_mutex.unlock();
+}
+
+class ScopeDatabaseLock {
+public:
+ ScopeDatabaseLock() {
+ db_lock();
+ }
+
+ ~ScopeDatabaseLock() {
+ db_unlock();
+ }
+};
+
+#endif
diff --git a/src/db/DatabasePlaylist.cxx b/src/db/DatabasePlaylist.cxx
new file mode 100644
index 000000000..f1cfdc874
--- /dev/null
+++ b/src/db/DatabasePlaylist.cxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabasePlaylist.hxx"
+#include "DatabaseSong.hxx"
+#include "Selection.hxx"
+#include "PlaylistFile.hxx"
+#include "Interface.hxx"
+#include "DetachedSong.hxx"
+#include "storage/StorageInterface.hxx"
+
+#include <functional>
+
+static bool
+AddSong(const Storage &storage, const char *playlist_path_utf8,
+ const LightSong &song, Error &error)
+{
+ return spl_append_song(playlist_path_utf8,
+ DatabaseDetachSong(storage, song),
+ error);
+}
+
+bool
+search_add_to_playlist(const Database &db, const Storage &storage,
+ const char *uri, const char *playlist_path_utf8,
+ const SongFilter *filter,
+ Error &error)
+{
+ const DatabaseSelection selection(uri, true, filter);
+
+ using namespace std::placeholders;
+ const auto f = std::bind(AddSong, std::ref(storage),
+ playlist_path_utf8, _1, _2);
+ return db.Visit(selection, f, error);
+}
diff --git a/src/db/DatabasePlaylist.hxx b/src/db/DatabasePlaylist.hxx
new file mode 100644
index 000000000..9dc3526bb
--- /dev/null
+++ b/src/db/DatabasePlaylist.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_PLAYLIST_HXX
+#define MPD_DATABASE_PLAYLIST_HXX
+
+#include "Compiler.h"
+
+class Database;
+class Storage;
+class SongFilter;
+class Error;
+
+gcc_nonnull(3,4)
+bool
+search_add_to_playlist(const Database &db, const Storage &storage,
+ const char *uri, const char *path_utf8,
+ const SongFilter *filter,
+ Error &error);
+
+#endif
diff --git a/src/db/DatabasePlugin.hxx b/src/db/DatabasePlugin.hxx
new file mode 100644
index 000000000..831101786
--- /dev/null
+++ b/src/db/DatabasePlugin.hxx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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
+
+struct config_param;
+class Error;
+class EventLoop;
+class DatabaseListener;
+class Database;
+
+struct DatabasePlugin {
+ /**
+ * This plugin requires a #Storage instance. It contains only
+ * cached metadata from files in the #Storage.
+ */
+ static constexpr unsigned FLAG_REQUIRE_STORAGE = 0x1;
+
+ const char *name;
+
+ unsigned flags;
+
+ /**
+ * Allocates and configures a database.
+ */
+ Database *(*create)(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
+ Error &error);
+
+ constexpr bool RequireStorage() const {
+ return flags & FLAG_REQUIRE_STORAGE;
+ }
+};
+
+#endif
diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx
new file mode 100644
index 000000000..498aedf97
--- /dev/null
+++ b/src/db/DatabasePrint.cxx
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabasePrint.hxx"
+#include "Selection.hxx"
+#include "SongFilter.hxx"
+#include "SongPrint.hxx"
+#include "TimePrint.hxx"
+#include "client/Client.hxx"
+#include "tag/Tag.hxx"
+#include "LightSong.hxx"
+#include "LightDirectory.hxx"
+#include "PlaylistInfo.hxx"
+#include "Interface.hxx"
+#include "fs/Traits.hxx"
+
+#include <functional>
+
+static const char *
+ApplyBaseFlag(const char *uri, bool base)
+{
+ if (base)
+ uri = PathTraitsUTF8::GetBase(uri);
+ return uri;
+}
+
+static void
+PrintDirectoryURI(Client &client, bool base, const LightDirectory &directory)
+{
+ client_printf(client, "directory: %s\n",
+ ApplyBaseFlag(directory.GetPath(), base));
+}
+
+static bool
+PrintDirectoryBrief(Client &client, bool base, const LightDirectory &directory)
+{
+ if (!directory.IsRoot())
+ PrintDirectoryURI(client, base, directory);
+
+ return true;
+}
+
+static bool
+PrintDirectoryFull(Client &client, bool base, const LightDirectory &directory)
+{
+ if (!directory.IsRoot()) {
+ PrintDirectoryURI(client, base, directory);
+
+ if (directory.mtime > 0)
+ time_print(client, "Last-Modified", directory.mtime);
+ }
+
+ return true;
+}
+
+static void
+print_playlist_in_directory(Client &client, bool base,
+ const char *directory,
+ const char *name_utf8)
+{
+ if (base || directory == nullptr)
+ client_printf(client, "playlist: %s\n",
+ ApplyBaseFlag(name_utf8, base));
+ else
+ client_printf(client, "playlist: %s/%s\n",
+ directory, name_utf8);
+}
+
+static void
+print_playlist_in_directory(Client &client, bool base,
+ const LightDirectory *directory,
+ const char *name_utf8)
+{
+ if (base || directory == nullptr || 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, bool base, const LightSong &song)
+{
+ song_print_uri(client, song, base);
+
+ if (song.tag->has_playlist)
+ /* this song file has an embedded CUE sheet */
+ print_playlist_in_directory(client, base,
+ song.directory, song.uri);
+
+ return true;
+}
+
+static bool
+PrintSongFull(Client &client, bool base, const LightSong &song)
+{
+ song_print_info(client, song, base);
+
+ if (song.tag->has_playlist)
+ /* this song file has an embedded CUE sheet */
+ print_playlist_in_directory(client, base,
+ song.directory, song.uri);
+
+ return true;
+}
+
+static bool
+PrintPlaylistBrief(Client &client, bool base,
+ const PlaylistInfo &playlist,
+ const LightDirectory &directory)
+{
+ print_playlist_in_directory(client, base,
+ &directory, playlist.name.c_str());
+ return true;
+}
+
+static bool
+PrintPlaylistFull(Client &client, bool base,
+ const PlaylistInfo &playlist,
+ const LightDirectory &directory)
+{
+ print_playlist_in_directory(client, base,
+ &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, bool base, Error &error)
+{
+ const Database *db = client.GetDatabase(error);
+ if (db == nullptr)
+ return false;
+
+ using namespace std::placeholders;
+ const auto d = selection.filter == nullptr
+ ? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief,
+ std::ref(client), base, _1)
+ : VisitDirectory();
+ const auto s = std::bind(full ? PrintSongFull : PrintSongBrief,
+ std::ref(client), base, _1);
+ const auto p = selection.filter == nullptr
+ ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief,
+ std::ref(client), base, _1, _2)
+ : VisitPlaylist();
+
+ return db->Visit(selection, d, s, p, error);
+}
+
+static bool
+PrintSongURIVisitor(Client &client, const LightSong &song)
+{
+ song_print_uri(client, song);
+
+ return true;
+}
+
+static bool
+PrintUniqueTag(Client &client, TagType tag_type,
+ const Tag &tag)
+{
+ const char *value = tag.GetValue(tag_type);
+ assert(value != nullptr);
+ client_printf(client, "%s: %s\n", tag_item_names[tag_type], value);
+
+ for (const auto &item : tag)
+ if (item.type != tag_type)
+ client_printf(client, "%s: %s\n",
+ tag_item_names[item.type], item.value);
+
+ return true;
+}
+
+bool
+PrintUniqueTags(Client &client, unsigned type, uint32_t group_mask,
+ const SongFilter *filter,
+ Error &error)
+{
+ const Database *db = client.GetDatabase(error);
+ 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,
+ std::ref(client), _1);
+ return db->Visit(selection, f, error);
+ } else {
+ assert(type < TAG_NUM_OF_ITEM_TYPES);
+
+ using namespace std::placeholders;
+ const auto f = std::bind(PrintUniqueTag, std::ref(client),
+ (TagType)type, _1);
+ return db->VisitUniqueTags(selection, (TagType)type,
+ group_mask,
+ f, error);
+ }
+}
diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx
new file mode 100644
index 000000000..2ab5e703d
--- /dev/null
+++ b/src/db/DatabasePrint.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_PRINT_H
+#define MPD_DB_PRINT_H
+
+#include "Compiler.h"
+
+#include <stdint.h>
+
+class SongFilter;
+struct DatabaseSelection;
+class Client;
+class Error;
+
+/**
+ * @param full print attributes/tags
+ * @param base print only base name of songs/directories?
+ */
+bool
+db_selection_print(Client &client, const DatabaseSelection &selection,
+ bool full, bool base, Error &error);
+
+bool
+PrintUniqueTags(Client &client, unsigned type, uint32_t group_mask,
+ const SongFilter *filter,
+ Error &error);
+
+#endif
diff --git a/src/db/DatabaseQueue.cxx b/src/db/DatabaseQueue.cxx
new file mode 100644
index 000000000..490678188
--- /dev/null
+++ b/src/db/DatabaseQueue.cxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabaseQueue.hxx"
+#include "DatabaseSong.hxx"
+#include "Interface.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "DetachedSong.hxx"
+
+#include <functional>
+
+static bool
+AddToQueue(Partition &partition, const LightSong &song, Error &error)
+{
+ const Storage &storage = *partition.instance.storage;
+ unsigned id =
+ partition.playlist.AppendSong(partition.pc,
+ DatabaseDetachSong(storage,
+ song),
+ error);
+ return id != 0;
+}
+
+bool
+AddFromDatabase(Partition &partition, const DatabaseSelection &selection,
+ Error &error)
+{
+ const Database *db = partition.instance.GetDatabase(error);
+ 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);
+}
diff --git a/src/db/DatabaseQueue.hxx b/src/db/DatabaseQueue.hxx
new file mode 100644
index 000000000..e653f973c
--- /dev/null
+++ b/src/db/DatabaseQueue.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_QUEUE_HXX
+#define MPD_DATABASE_QUEUE_HXX
+
+struct Partition;
+struct DatabaseSelection;
+class Error;
+
+bool
+AddFromDatabase(Partition &partition, const DatabaseSelection &selection,
+ Error &error);
+
+#endif
diff --git a/src/db/DatabaseSong.cxx b/src/db/DatabaseSong.cxx
new file mode 100644
index 000000000..dd27aa8b3
--- /dev/null
+++ b/src/db/DatabaseSong.cxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabaseSong.hxx"
+#include "LightSong.hxx"
+#include "Interface.hxx"
+#include "DetachedSong.hxx"
+#include "storage/StorageInterface.hxx"
+
+#include <assert.h>
+
+DetachedSong
+DatabaseDetachSong(const Storage &storage, const LightSong &song)
+{
+ DetachedSong detached(song);
+ assert(detached.IsInDatabase());
+
+ if (!detached.HasRealURI()) {
+ const auto uri = song.GetURI();
+ detached.SetRealURI(storage.MapUTF8(uri.c_str()));
+ }
+
+ return detached;
+}
+
+DetachedSong *
+DatabaseDetachSong(const Database &db, const Storage &storage, const char *uri,
+ Error &error)
+{
+ const LightSong *tmp = db.GetSong(uri, error);
+ if (tmp == nullptr)
+ return nullptr;
+
+ DetachedSong *song = new DetachedSong(DatabaseDetachSong(storage,
+ *tmp));
+ db.ReturnSong(tmp);
+ return song;
+}
diff --git a/src/db/DatabaseSong.hxx b/src/db/DatabaseSong.hxx
new file mode 100644
index 000000000..4daaf4047
--- /dev/null
+++ b/src/db/DatabaseSong.hxx
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_SONG_HXX
+#define MPD_DATABASE_SONG_HXX
+
+#include "Compiler.h"
+
+struct LightSong;
+class Database;
+class Storage;
+class DetachedSong;
+class Error;
+
+/**
+ * "Detach" the #Song object, i.e. convert it to a #DetachedSong
+ * instance.
+ */
+gcc_pure
+DetachedSong
+DatabaseDetachSong(const Storage &storage, const LightSong &song);
+
+/**
+ * Look up a song in the database and convert it to a #DetachedSong
+ * instance. The caller is responsible for freeing it.
+ *
+ * @return nullptr on error
+ */
+gcc_malloc gcc_nonnull_all
+DetachedSong *
+DatabaseDetachSong(const Database &db, const Storage &storage, const char *uri,
+ Error &error);
+
+#endif
diff --git a/src/db/Helpers.cxx b/src/db/Helpers.cxx
new file mode 100644
index 000000000..add4bb98e
--- /dev/null
+++ b/src/db/Helpers.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Helpers.hxx"
+#include "Stats.hxx"
+#include "Interface.hxx"
+#include "LightSong.hxx"
+#include "tag/Tag.hxx"
+
+#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 void
+StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
+ const Tag &tag)
+{
+ if (!tag.duration.IsNegative())
+ stats.total_duration += tag.duration;
+
+ for (const auto &item : tag) {
+ switch (item.type) {
+ case TAG_ARTIST:
+#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+ artists.emplace(item.value);
+#else
+ artists.insert(item.value);
+#endif
+ break;
+
+ case TAG_ALBUM:
+#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+ albums.emplace(item.value);
+#else
+ albums.insert(item.value);
+#endif
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static bool
+StatsVisitSong(DatabaseStats &stats, StringSet &artists, StringSet &albums,
+ const LightSong &song)
+{
+ ++stats.song_count;
+
+ StatsVisitTag(stats, artists, albums, *song.tag);
+
+ return true;
+}
+
+bool
+GetStats(const Database &db, const DatabaseSelection &selection,
+ DatabaseStats &stats, Error &error)
+{
+ 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))
+ return false;
+
+ stats.artist_count = artists.size();
+ stats.album_count = albums.size();
+ return true;
+}
diff --git a/src/db/Helpers.hxx b/src/db/Helpers.hxx
new file mode 100644
index 000000000..651bac0e0
--- /dev/null
+++ b/src/db/Helpers.hxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MEMORY_DATABASE_PLUGIN_HXX
+#define MPD_MEMORY_DATABASE_PLUGIN_HXX
+
+class Error;
+class Database;
+struct DatabaseSelection;
+struct DatabaseStats;
+
+bool
+GetStats(const Database &db, const DatabaseSelection &selection,
+ DatabaseStats &stats, Error &error);
+
+#endif
diff --git a/src/db/Interface.hxx b/src/db/Interface.hxx
new file mode 100644
index 000000000..152928c79
--- /dev/null
+++ b/src/db/Interface.hxx
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_INTERFACE_HXX
+#define MPD_DATABASE_INTERFACE_HXX
+
+#include "Visitor.hxx"
+#include "tag/TagType.h"
+#include "Compiler.h"
+
+#include <time.h>
+#include <stdint.h>
+
+struct DatabasePlugin;
+struct DatabaseStats;
+struct DatabaseSelection;
+struct LightSong;
+class Error;
+
+class Database {
+ const DatabasePlugin &plugin;
+
+public:
+ Database(const DatabasePlugin &_plugin)
+ :plugin(_plugin) {}
+
+ /**
+ * Free instance data.
+ */
+ virtual ~Database() {}
+
+ const DatabasePlugin &GetPlugin() const {
+ return plugin;
+ }
+
+ bool IsPlugin(const DatabasePlugin &other) const {
+ return &plugin == &other;
+ }
+
+ /**
+ * Open the database. Read it into memory if applicable.
+ */
+ virtual bool Open(gcc_unused Error &error) {
+ 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 const LightSong *GetSong(const char *uri_utf8,
+ Error &error) const = 0;
+
+ /**
+ * Mark the song object as "unused". Call this on objects
+ * returned by GetSong().
+ */
+ virtual void ReturnSong(const LightSong *song) const = 0;
+
+ /**
+ * Visit the selected entities.
+ */
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const = 0;
+
+ bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ Error &error) const {
+ return Visit(selection, visit_directory, visit_song,
+ VisitPlaylist(), error);
+ }
+
+ bool Visit(const DatabaseSelection &selection, VisitSong visit_song,
+ Error &error) const {
+ return Visit(selection, VisitDirectory(), visit_song, error);
+ }
+
+ /**
+ * Visit all unique tag values.
+ */
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const = 0;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const = 0;
+
+ /**
+ * Update the database. Returns the job id on success, 0 on
+ * error (with #Error set) and 0 if not implemented (#Error
+ * not set).
+ */
+ virtual unsigned Update(gcc_unused const char *uri_utf8,
+ gcc_unused bool discard,
+ gcc_unused Error &error) {
+ /* not implemented: return 0 and don't set an Error */
+ return 0;
+ }
+
+ /**
+ * Returns the time stamp of the last database update.
+ * Returns 0 if that is not not known/available.
+ */
+ gcc_pure
+ virtual time_t GetUpdateStamp() const = 0;
+};
+
+#endif
diff --git a/src/db/LightDirectory.hxx b/src/db/LightDirectory.hxx
new file mode 100644
index 000000000..d134151a4
--- /dev/null
+++ b/src/db/LightDirectory.hxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_LIGHT_DIRECTORY_HXX
+#define MPD_LIGHT_DIRECTORY_HXX
+
+#include "Compiler.h"
+
+#include <string>
+
+#include <time.h>
+
+struct Tag;
+
+/**
+ * A reference to a directory. Unlike the #Directory class, this one
+ * consists only of pointers. It is supposed to be as light as
+ * possible while still providing all the information MPD has about a
+ * directory. This class does not manage any memory, and the pointers
+ * become invalid quickly. Only to be used to pass around during
+ * well-defined situations.
+ */
+struct LightDirectory {
+ const char *uri;
+
+ time_t mtime;
+
+ constexpr LightDirectory(const char *_uri, time_t _mtime)
+ :uri(_uri), mtime(_mtime) {}
+
+ static constexpr LightDirectory Root() {
+ return LightDirectory("", 0);
+ }
+
+ bool IsRoot() const {
+ return *uri == 0;
+ }
+
+ gcc_pure
+ const char *GetPath() const {
+ return uri;
+ }
+};
+
+#endif
diff --git a/src/db/LightSong.cxx b/src/db/LightSong.cxx
new file mode 100644
index 000000000..5cdebc133
--- /dev/null
+++ b/src/db/LightSong.cxx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "LightSong.hxx"
+#include "tag/Tag.hxx"
+
+SignedSongTime
+LightSong::GetDuration() const
+{
+ SongTime a = start_time, b = end_time;
+ if (!b.IsPositive()) {
+ if (tag->duration.IsNegative())
+ return tag->duration;
+
+ b = SongTime(tag->duration);
+ }
+
+ return SignedSongTime(b - a);
+}
diff --git a/src/db/LightSong.hxx b/src/db/LightSong.hxx
new file mode 100644
index 000000000..bbd449fbe
--- /dev/null
+++ b/src/db/LightSong.hxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_LIGHT_SONG_HXX
+#define MPD_LIGHT_SONG_HXX
+
+#include "Chrono.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+#include <time.h>
+
+struct Tag;
+
+/**
+ * A reference to a song file. Unlike the other "Song" classes in the
+ * MPD code base, this one consists only of pointers. It is supposed
+ * to be as light as possible while still providing all the
+ * information MPD has about a song file. This class does not manage
+ * any memory, and the pointers become invalid quickly. Only to be
+ * used to pass around during well-defined situations.
+ */
+struct LightSong {
+ /**
+ * If this is not nullptr, then it denotes a prefix for the
+ * #uri. To build the full URI, join directory and uri with a
+ * slash.
+ */
+ const char *directory;
+
+ const char *uri;
+
+ /**
+ * The "real" URI, the one to be used for opening the
+ * resource. If this attribute is nullptr, then #uri (and
+ * #directory) shall be used.
+ *
+ * This attribute is used for songs from the database which
+ * have a relative URI.
+ */
+ const char *real_uri;
+
+ /**
+ * Must not be nullptr.
+ */
+ const Tag *tag;
+
+ time_t mtime;
+
+ /**
+ * Start of this sub-song within the file.
+ */
+ SongTime start_time;
+
+ /**
+ * End of this sub-song within the file.
+ * Unused if zero.
+ */
+ SongTime end_time;
+
+ gcc_pure
+ std::string GetURI() const {
+ if (directory == nullptr)
+ return std::string(uri);
+
+ std::string result(directory);
+ result.push_back('/');
+ result.append(uri);
+ return result;
+ }
+
+ gcc_pure
+ SignedSongTime GetDuration() const;
+};
+
+#endif
diff --git a/src/db/PlaylistInfo.hxx b/src/db/PlaylistInfo.hxx
new file mode 100644
index 000000000..baa6cc361
--- /dev/null
+++ b/src/db/PlaylistInfo.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_INFO_HXX
+#define MPD_PLAYLIST_INFO_HXX
+
+#include "check.h"
+#include "Compiler.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/db/PlaylistVector.cxx b/src/db/PlaylistVector.cxx
new file mode 100644
index 000000000..82a3519d9
--- /dev/null
+++ b/src/db/PlaylistVector.cxx
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistVector.hxx"
+#include "db/DatabaseLock.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+
+PlaylistVector::iterator
+PlaylistVector::find(const char *name)
+{
+ assert(holding_db_lock());
+ assert(name != nullptr);
+
+ 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/db/PlaylistVector.hxx b/src/db/PlaylistVector.hxx
new file mode 100644
index 000000000..accd4fd42
--- /dev/null
+++ b/src/db/PlaylistVector.hxx
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_VECTOR_HXX
+#define MPD_PLAYLIST_VECTOR_HXX
+
+#include "db/PlaylistInfo.hxx"
+#include "Compiler.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/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx
deleted file mode 100644
index 1751e0950..000000000
--- a/src/db/ProxyDatabasePlugin.cxx
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ProxyDatabasePlugin.hxx"
-#include "DatabasePlugin.hxx"
-#include "DatabaseSelection.hxx"
-#include "DatabaseError.hxx"
-#include "PlaylistVector.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
-#include "SongFilter.hxx"
-#include "Compiler.h"
-#include "ConfigData.hxx"
-#include "tag/TagBuilder.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "protocol/Ack.hxx"
-
-#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;
-
- /* this is mutable because GetStats() must be "const" */
- mutable time_t update_stamp;
-
-public:
- static Database *Create(const config_param &param,
- Error &error);
-
- virtual bool Open(Error &error) override;
- virtual void Close() override;
- virtual Song *GetSong(const char *uri_utf8,
- Error &error) const override;
- void ReturnSong(Song *song) const override;
-
- virtual bool Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- VisitPlaylist visit_playlist,
- Error &error) const override;
-
- virtual bool VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const override;
-
- virtual bool GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats,
- Error &error) const override;
-
- virtual time_t GetUpdateStamp() const override {
- return update_stamp;
- }
-
-private:
- bool Configure(const config_param &param, Error &error);
-
- bool Connect(Error &error);
- bool CheckConnection(Error &error);
- bool EnsureConnected(Error &error);
-};
-
-static constexpr Domain libmpdclient_domain("libmpdclient");
-
-static constexpr struct {
- TagType 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 }
-};
-
-gcc_const
-static enum mpd_tag_type
-Convert(TagType 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, Error &error)
-{
- const auto code = mpd_connection_get_error(connection);
- if (code == MPD_ERROR_SUCCESS)
- return true;
-
- if (code == MPD_ERROR_SERVER) {
- /* libmpdclient's "enum mpd_server_error" is the same
- as our "enum ack" */
- const auto server_error =
- mpd_connection_get_server_error(connection);
- error.Set(ack_domain, (int)server_error,
- mpd_connection_get_error_message(connection));
- } else {
- error.Set(libmpdclient_domain, (int)code,
- mpd_connection_get_error_message(connection));
- }
-
- mpd_connection_clear_error(connection);
- return false;
-}
-
-static bool
-SendConstraints(mpd_connection *connection, const SongFilter::Item &item)
-{
- switch (item.GetTag()) {
- mpd_tag_type tag;
-
-#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
- case LOCATE_TAG_BASE_TYPE:
- if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0)
- /* requires MPD 0.18 */
- return true;
-
- return mpd_search_add_base_constraint(connection,
- MPD_OPERATOR_DEFAULT,
- item.GetValue().c_str());
-#endif
-
- case LOCATE_TAG_FILE_TYPE:
- return mpd_search_add_uri_constraint(connection,
- MPD_OPERATOR_DEFAULT,
- item.GetValue().c_str());
-
- case LOCATE_TAG_ANY_TYPE:
- return mpd_search_add_any_tag_constraint(connection,
- MPD_OPERATOR_DEFAULT,
- item.GetValue().c_str());
-
- default:
- tag = Convert(TagType(item.GetTag()));
- if (tag == MPD_TAG_COUNT)
- return true;
-
- return mpd_search_add_tag_constraint(connection,
- MPD_OPERATOR_DEFAULT,
- tag,
- item.GetValue().c_str());
- }
-}
-
-static bool
-SendConstraints(mpd_connection *connection, const SongFilter &filter)
-{
- for (const auto &i : filter.GetItems())
- if (!SendConstraints(connection, i))
- return false;
-
- return true;
-}
-
-static bool
-SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
-{
-#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
- if (!selection.uri.empty() &&
- mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) {
- /* requires MPD 0.18 */
- if (!mpd_search_add_base_constraint(connection,
- MPD_OPERATOR_DEFAULT,
- selection.uri.c_str()))
- return false;
- }
-#endif
-
- if (selection.filter != nullptr &&
- !SendConstraints(connection, *selection.filter))
- return false;
-
- return true;
-}
-
-Database *
-ProxyDatabase::Create(const config_param &param, Error &error)
-{
- ProxyDatabase *db = new ProxyDatabase();
- if (!db->Configure(param, error)) {
- delete db;
- db = nullptr;
- }
-
- return db;
-}
-
-bool
-ProxyDatabase::Configure(const config_param &param, gcc_unused Error &error)
-{
- host = param.GetBlockValue("host", "");
- port = param.GetBlockValue("port", 0u);
-
- return true;
-}
-
-bool
-ProxyDatabase::Open(Error &error)
-{
- if (!Connect(error))
- return false;
-
- root = Directory::NewRoot();
- update_stamp = 0;
-
- return true;
-}
-
-void
-ProxyDatabase::Close()
-{
- root->Free();
-
- if (connection != nullptr)
- mpd_connection_free(connection);
-}
-
-bool
-ProxyDatabase::Connect(Error &error)
-{
- const char *_host = host.empty() ? nullptr : host.c_str();
- connection = mpd_connection_new(_host, port, 0);
- if (connection == nullptr) {
- error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM,
- "Out of memory");
- return false;
- }
-
- if (!CheckError(connection, error)) {
- if (connection != nullptr) {
- mpd_connection_free(connection);
- connection = nullptr;
- }
-
- return false;
- }
-
- return true;
-}
-
-bool
-ProxyDatabase::CheckConnection(Error &error)
-{
- assert(connection != nullptr);
-
- if (!mpd_connection_clear_error(connection)) {
- mpd_connection_free(connection);
- return Connect(error);
- }
-
- return true;
-}
-
-bool
-ProxyDatabase::EnsureConnected(Error &error)
-{
- return connection != nullptr
- ? CheckConnection(error)
- : Connect(error);
-}
-
-static Song *
-Convert(const struct mpd_song *song);
-
-Song *
-ProxyDatabase::GetSong(const char *uri, Error &error) const
-{
- // TODO: eliminate the const_cast
- if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
- return nullptr;
-
- if (!mpd_send_list_meta(connection, uri)) {
- CheckError(connection, error);
- return nullptr;
- }
-
- struct mpd_song *song = mpd_recv_song(connection);
- Song *song2 = song != nullptr
- ? Convert(song)
- : nullptr;
- if (song != nullptr)
- mpd_song_free(song);
- if (!mpd_response_finish(connection)) {
- if (song2 != nullptr)
- song2->Free();
-
- CheckError(connection, error);
- return nullptr;
- }
-
- if (song2 == nullptr)
- error.Format(db_domain, 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, const SongFilter *filter,
- VisitDirectory visit_directory, VisitSong visit_song,
- VisitPlaylist visit_playlist, Error &error);
-
-static bool
-Visit(struct mpd_connection *connection,
- bool recursive, const SongFilter *filter,
- const struct mpd_directory *directory,
- VisitDirectory visit_directory, VisitSong visit_song,
- VisitPlaylist visit_playlist, Error &error)
-{
- const char *path = mpd_directory_get_path(directory);
-
- if (visit_directory) {
- Directory *d = Directory::NewGeneric(path, &detached_root);
- bool success = visit_directory(*d, error);
- d->Free();
- if (!success)
- return false;
- }
-
- if (recursive &&
- !Visit(connection, path, recursive, filter,
- visit_directory, visit_song, visit_playlist, error))
- return false;
-
- return true;
-}
-
-static void
-Copy(TagBuilder &tag, TagType 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 == nullptr)
- 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);
-
-#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
- s->start_ms = mpd_song_get_start(song) * 1000;
- s->end_ms = mpd_song_get_end(song) * 1000;
-#else
- s->start_ms = s->end_ms = 0;
-#endif
-
- TagBuilder tag;
- tag.SetTime(mpd_song_get_duration(song));
-
- for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
- Copy(tag, i->d, song, i->s);
-
- s->tag = tag.Commit();
-
- return s;
-}
-
-gcc_pure
-static bool
-Match(const SongFilter *filter, const Song &song)
-{
- return filter == nullptr || filter->Match(song);
-}
-
-static bool
-Visit(const SongFilter *filter,
- const struct mpd_song *song,
- VisitSong visit_song, Error &error)
-{
- if (!visit_song)
- return true;
-
- Song *s = Convert(song);
- bool success = !Match(filter, *s) || visit_song(*s, error);
- s->Free();
-
- return success;
-}
-
-static bool
-Visit(const struct mpd_playlist *playlist,
- VisitPlaylist visit_playlist, Error &error)
-{
- 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);
-}
-
-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)) != nullptr)
- entities.push_back(ProxyEntity(entity));
-
- mpd_response_finish(connection);
- return entities;
-}
-
-static bool
-Visit(struct mpd_connection *connection, const char *uri,
- bool recursive, const SongFilter *filter,
- VisitDirectory visit_directory, VisitSong visit_song,
- VisitPlaylist visit_playlist, Error &error)
-{
- if (!mpd_send_list_meta(connection, uri))
- return CheckError(connection, error);
-
- std::list<ProxyEntity> entities(ReceiveEntities(connection));
- if (!CheckError(connection, error))
- 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, filter,
- mpd_entity_get_directory(entity),
- visit_directory, visit_song, visit_playlist,
- error))
- return false;
- break;
-
- case MPD_ENTITY_TYPE_SONG:
- if (!Visit(filter,
- mpd_entity_get_song(entity), visit_song,
- error))
- return false;
- break;
-
- case MPD_ENTITY_TYPE_PLAYLIST:
- if (!Visit(mpd_entity_get_playlist(entity),
- visit_playlist, error))
- return false;
- break;
- }
- }
-
- return CheckError(connection, error);
-}
-
-static bool
-SearchSongs(struct mpd_connection *connection,
- const DatabaseSelection &selection,
- VisitSong visit_song,
- Error &error)
-{
- assert(selection.recursive);
- assert(visit_song);
-
- const bool exact = selection.filter == nullptr ||
- !selection.filter->HasFoldCase();
-
- if (!mpd_search_db_songs(connection, exact) ||
- !SendConstraints(connection, selection) ||
- !mpd_search_commit(connection))
- return CheckError(connection, error);
-
- bool result = true;
- struct mpd_song *song;
- while (result && (song = mpd_recv_song(connection)) != nullptr) {
- Song *song2 = Convert(song);
- mpd_song_free(song);
-
- result = !Match(selection.filter, *song2) ||
- visit_song(*song2, error);
- song2->Free();
- }
-
- mpd_response_finish(connection);
- return result && CheckError(connection, error);
-}
-
-/**
- * Check whether we can use the "base" constraint. Requires
- * libmpdclient 2.9 and MPD 0.18.
- */
-gcc_pure
-static bool
-ServerSupportsSearchBase(const struct mpd_connection *connection)
-{
-#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
- return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
-#else
- (void)connection;
-
- return false;
-#endif
-}
-
-bool
-ProxyDatabase::Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- VisitPlaylist visit_playlist,
- Error &error) const
-{
- // TODO: eliminate the const_cast
- if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
- return false;
-
- if (!visit_directory && !visit_playlist && selection.recursive &&
- (ServerSupportsSearchBase(connection)
- ? !selection.IsEmpty()
- : selection.HasOtherThanBase()))
- /* this optimized code path can only be used under
- certain conditions */
- return ::SearchSongs(connection, selection, visit_song, error);
-
- /* fall back to recursive walk (slow!) */
- return ::Visit(connection, selection.uri.c_str(),
- selection.recursive, selection.filter,
- visit_directory, visit_song, visit_playlist,
- error);
-}
-
-bool
-ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const
-{
- // TODO: eliminate the const_cast
- if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
- return false;
-
- enum mpd_tag_type tag_type2 = Convert(tag_type);
- if (tag_type2 == MPD_TAG_COUNT) {
- error.Set(libmpdclient_domain, "Unsupported tag");
- return false;
- }
-
- if (!mpd_search_db_tags(connection, tag_type2))
- return CheckError(connection, error);
-
- if (!SendConstraints(connection, selection))
- return CheckError(connection, error);
-
- if (!mpd_search_commit(connection))
- return CheckError(connection, error);
-
- bool result = true;
-
- struct mpd_pair *pair;
- while (result &&
- (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) {
- result = visit_string(pair->value, error);
- mpd_return_pair(connection, pair);
- }
-
- return mpd_response_finish(connection) &&
- CheckError(connection, error) &&
- result;
-}
-
-bool
-ProxyDatabase::GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats, Error &error) const
-{
- // TODO: match
- (void)selection;
-
- // TODO: eliminate the const_cast
- if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
- return false;
-
- struct mpd_stats *stats2 =
- mpd_run_stats(connection);
- if (stats2 == nullptr)
- return CheckError(connection, error);
-
- update_stamp = (time_t)mpd_stats_get_db_update_time(stats2);
-
- 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
deleted file mode 100644
index 576c01c69..000000000
--- a/src/db/ProxyDatabasePlugin.hxx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX
-#define MPD_PROXY_DATABASE_PLUGIN_HXX
-
-struct DatabasePlugin;
-
-extern const DatabasePlugin proxy_db_plugin;
-
-#endif
diff --git a/src/db/Registry.cxx b/src/db/Registry.cxx
new file mode 100644
index 000000000..5681a9b82
--- /dev/null
+++ b/src/db/Registry.cxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Registry.hxx"
+#include "DatabasePlugin.hxx"
+#include "plugins/simple/SimpleDatabasePlugin.hxx"
+#include "plugins/ProxyDatabasePlugin.hxx"
+#include "plugins/upnp/UpnpDatabasePlugin.hxx"
+
+#include <string.h>
+
+const DatabasePlugin *const database_plugins[] = {
+ &simple_db_plugin,
+#ifdef HAVE_LIBMPDCLIENT
+ &proxy_db_plugin,
+#endif
+#ifdef HAVE_LIBUPNP
+ &upnp_db_plugin,
+#endif
+ nullptr
+};
+
+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/db/Registry.hxx b/src/db/Registry.hxx
new file mode 100644
index 000000000..050842e21
--- /dev/null
+++ b/src/db/Registry.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_REGISTRY_HXX
+#define MPD_DATABASE_REGISTRY_HXX
+
+#include "Compiler.h"
+
+struct DatabasePlugin;
+
+/**
+ * nullptr 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/db/Selection.cxx b/src/db/Selection.cxx
new file mode 100644
index 000000000..a886916cb
--- /dev/null
+++ b/src/db/Selection.cxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Selection.hxx"
+#include "SongFilter.hxx"
+
+DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
+ const SongFilter *_filter)
+ :uri(_uri), recursive(_recursive), filter(_filter)
+{
+ /* optimization: if the caller didn't specify a base URI, pick
+ the one from SongFilter */
+ if (uri.empty() && filter != nullptr)
+ uri = filter->GetBase();
+}
+
+bool
+DatabaseSelection::IsEmpty() const
+{
+ return uri.empty() && (filter == nullptr || filter->IsEmpty());
+}
+
+bool
+DatabaseSelection::HasOtherThanBase() const
+{
+ return filter != nullptr && filter->HasOtherThanBase();
+}
+
+bool
+DatabaseSelection::Match(const LightSong &song) const
+{
+ return filter == nullptr || filter->Match(song);
+}
diff --git a/src/db/Selection.hxx b/src/db/Selection.hxx
new file mode 100644
index 000000000..9802603fc
--- /dev/null
+++ b/src/db/Selection.hxx
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_SELECTION_HXX
+#define MPD_DATABASE_SELECTION_HXX
+
+#include "Compiler.h"
+
+#include <string>
+
+class SongFilter;
+struct LightSong;
+
+struct DatabaseSelection {
+ /**
+ * The base URI of the search (UTF-8). Must not begin or end
+ * with a slash. An empty string searches the whole database.
+ */
+ std::string uri;
+
+ /**
+ * Recursively search all sub directories?
+ */
+ bool recursive;
+
+ const SongFilter *filter;
+
+ DatabaseSelection(const char *_uri, bool _recursive,
+ const SongFilter *_filter=nullptr);
+
+ gcc_pure
+ bool IsEmpty() const;
+
+ /**
+ * Does this selection contain constraints other than "base"?
+ */
+ gcc_pure
+ bool HasOtherThanBase() const;
+
+ gcc_pure
+ bool Match(const LightSong &song) const;
+};
+
+#endif
diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx
deleted file mode 100644
index e7ea7a62d..000000000
--- a/src/db/SimpleDatabasePlugin.cxx
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SimpleDatabasePlugin.hxx"
-#include "DatabaseSelection.hxx"
-#include "DatabaseHelpers.hxx"
-#include "Directory.hxx"
-#include "SongFilter.hxx"
-#include "DatabaseSave.hxx"
-#include "DatabaseLock.hxx"
-#include "DatabaseError.hxx"
-#include "TextFile.hxx"
-#include "ConfigData.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <sys/types.h>
-#include <errno.h>
-
-static constexpr Domain simple_db_domain("simple_db");
-
-Database *
-SimpleDatabase::Create(const config_param &param, Error &error)
-{
- SimpleDatabase *db = new SimpleDatabase();
- if (!db->Configure(param, error)) {
- delete db;
- db = nullptr;
- }
-
- return db;
-}
-
-bool
-SimpleDatabase::Configure(const config_param &param, Error &error)
-{
- path = param.GetBlockPath("path", error);
- if (path.IsNull()) {
- if (!error.IsDefined())
- error.Set(simple_db_domain,
- "No \"path\" parameter specified");
- return false;
- }
-
- path_utf8 = path.ToUTF8();
-
- return true;
-}
-
-bool
-SimpleDatabase::Check(Error &error) const
-{
- assert(!path.IsNull());
-
- /* 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 auto dirPath = path.GetDirectoryName();
-
- /* Check that the parent part of the path is a directory */
- struct stat st;
- if (!StatFile(dirPath, st)) {
- error.FormatErrno("Couldn't stat parent directory of db file "
- "\"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- if (!S_ISDIR(st.st_mode)) {
- error.Format(simple_db_domain,
- "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)) {
- const int e = errno;
- const std::string dirPath_utf8 = dirPath.ToUTF8();
- error.FormatErrno(e, "Can't create db file in \"%s\"",
- dirPath_utf8.c_str());
- return false;
- }
-
- return true;
- }
-
- /* Path exists, now check if it's a regular file */
- struct stat st;
- if (!StatFile(path, st)) {
- error.FormatErrno("Couldn't stat db file \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- if (!S_ISREG(st.st_mode)) {
- error.Format(simple_db_domain,
- "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)) {
- error.FormatErrno("Can't open db file \"%s\" for reading/writing",
- path_utf8.c_str());
- return false;
- }
-
- return true;
-}
-
-bool
-SimpleDatabase::Load(Error &error)
-{
- assert(!path.IsNull());
- assert(root != nullptr);
-
- TextFile file(path);
- if (file.HasFailed()) {
- error.FormatErrno("Failed to open database file \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- if (!db_load_internal(file, *root, error))
- return false;
-
- struct stat st;
- if (StatFile(path, st))
- mtime = st.st_mtime;
-
- return true;
-}
-
-bool
-SimpleDatabase::Open(Error &error)
-{
- root = Directory::NewRoot();
- mtime = 0;
-
-#ifndef NDEBUG
- borrowed_song_count = 0;
-#endif
-
- if (!Load(error)) {
- root->Free();
-
- LogError(error);
- error.Clear();
-
- if (!Check(error))
- return false;
-
- root = Directory::NewRoot();
- }
-
- return true;
-}
-
-void
-SimpleDatabase::Close()
-{
- assert(root != nullptr);
- assert(borrowed_song_count == 0);
-
- root->Free();
-}
-
-Song *
-SimpleDatabase::GetSong(const char *uri, Error &error) const
-{
- assert(root != nullptr);
-
- db_lock();
- Song *song = root->LookupSong(uri);
- db_unlock();
- if (song == nullptr)
- error.Format(db_domain, 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
-}
-
-gcc_pure
-const Directory *
-SimpleDatabase::LookupDirectory(const char *uri) const
-{
- assert(root != nullptr);
- assert(uri != nullptr);
-
- ScopeDatabaseLock protect;
- return root->LookupDirectory(uri);
-}
-
-bool
-SimpleDatabase::Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- VisitPlaylist visit_playlist,
- Error &error) const
-{
- ScopeDatabaseLock protect;
-
- const Directory *directory = root->LookupDirectory(selection.uri.c_str());
- if (directory == nullptr) {
- if (visit_song) {
- Song *song = root->LookupSong(selection.uri.c_str());
- if (song != nullptr)
- return !selection.Match(*song) ||
- visit_song(*song, error);
- }
-
- error.Set(db_domain, DB_NOT_FOUND, "No such directory");
- return false;
- }
-
- if (selection.recursive && visit_directory &&
- !visit_directory(*directory, error))
- return false;
-
- return directory->Walk(selection.recursive, selection.filter,
- visit_directory, visit_song, visit_playlist,
- error);
-}
-
-bool
-SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const
-{
- return ::VisitUniqueTags(*this, selection, tag_type, visit_string,
- error);
-}
-
-bool
-SimpleDatabase::GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats, Error &error) const
-{
- return ::GetStats(*this, selection, stats, error);
-}
-
-bool
-SimpleDatabase::Save(Error &error)
-{
- db_lock();
-
- LogDebug(simple_db_domain, "removing empty directories from DB");
- root->PruneEmpty();
-
- LogDebug(simple_db_domain, "sorting DB");
- root->Sort();
-
- db_unlock();
-
- LogDebug(simple_db_domain, "writing DB");
-
- FILE *fp = FOpen(path, FOpenMode::WriteText);
- if (!fp) {
- error.FormatErrno("unable to write to db file \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- db_save_internal(fp, *root);
-
- if (ferror(fp)) {
- error.SetErrno("Failed to write to database file");
- 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
deleted file mode 100644
index 6424feaa6..000000000
--- a/src/db/SimpleDatabasePlugin.hxx
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_SIMPLE_DATABASE_PLUGIN_HXX
-#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
-
-#include "DatabasePlugin.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "Compiler.h"
-
-#include <cassert>
-
-struct Directory;
-
-class SimpleDatabase : public Database {
- AllocatedPath path;
- std::string path_utf8;
-
- Directory *root;
-
- time_t mtime;
-
-#ifndef NDEBUG
- unsigned borrowed_song_count;
-#endif
-
- SimpleDatabase()
- :path(AllocatedPath::Null()) {}
-
-public:
- gcc_pure
- Directory *GetRoot() {
- assert(root != NULL);
-
- return root;
- }
-
- bool Save(Error &error);
-
- static Database *Create(const config_param &param,
- Error &error);
-
- virtual bool Open(Error &error) override;
- virtual void Close() override;
-
- virtual Song *GetSong(const char *uri_utf8,
- Error &error) const override;
- void ReturnSong(Song *song) const override;
-
- virtual bool Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- VisitPlaylist visit_playlist,
- Error &error) const override;
-
- virtual bool VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const override;
-
- virtual bool GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats,
- Error &error) const override;
-
- virtual time_t GetUpdateStamp() const override {
- return mtime;
- }
-
-protected:
- bool Configure(const config_param &param, Error &error);
-
- gcc_pure
- bool Check(Error &error) const;
-
- bool Load(Error &error);
-
- gcc_pure
- const Directory *LookupDirectory(const char *uri) const;
-};
-
-extern const DatabasePlugin simple_db_plugin;
-
-#endif
diff --git a/src/db/Stats.hxx b/src/db/Stats.hxx
new file mode 100644
index 000000000..131a5dc47
--- /dev/null
+++ b/src/db/Stats.hxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_STATS_HXX
+#define MPD_DATABASE_STATS_HXX
+
+#include "Chrono.hxx"
+
+struct DatabaseStats {
+ /**
+ * Number of songs.
+ */
+ unsigned song_count;
+
+ /**
+ * Total duration of all songs (in seconds).
+ */
+ std::chrono::duration<std::uint64_t, SongTime::period> 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 = total_duration.zero();
+ artist_count = album_count = 0;
+ }
+};
+
+#endif
diff --git a/src/db/UniqueTags.cxx b/src/db/UniqueTags.cxx
new file mode 100644
index 000000000..589dc936d
--- /dev/null
+++ b/src/db/UniqueTags.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "UniqueTags.hxx"
+#include "Interface.hxx"
+#include "LightSong.hxx"
+#include "tag/Set.hxx"
+
+#include <functional>
+
+#include <assert.h>
+
+static bool
+CollectTags(TagSet &set, TagType tag_type, uint32_t group_mask,
+ const LightSong &song)
+{
+ assert(song.tag != nullptr);
+ const Tag &tag = *song.tag;
+
+ set.InsertUnique(tag, tag_type, group_mask);
+ return true;
+}
+
+bool
+VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error)
+{
+ TagSet set;
+
+ using namespace std::placeholders;
+ const auto f = std::bind(CollectTags, std::ref(set),
+ tag_type, group_mask, _1);
+ if (!db.Visit(selection, f, error))
+ return false;
+
+ for (const auto &value : set)
+ if (!visit_tag(value, error))
+ return false;
+
+ return true;
+}
diff --git a/src/db/UniqueTags.hxx b/src/db/UniqueTags.hxx
new file mode 100644
index 000000000..61004fc56
--- /dev/null
+++ b/src/db/UniqueTags.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_UNIQUE_TAGS_HXX
+#define MPD_DB_UNIQUE_TAGS_HXX
+
+#include "Visitor.hxx"
+#include "tag/TagType.h"
+
+#include <stdint.h>
+
+class Error;
+class Database;
+struct DatabaseSelection;
+
+bool
+VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error);
+
+#endif
diff --git a/src/db/Uri.hxx b/src/db/Uri.hxx
new file mode 100644
index 000000000..04960ba80
--- /dev/null
+++ b/src/db/Uri.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_URI_HXX
+#define MPD_DB_URI_HXX
+
+static inline bool
+isRootDirectory(const char *name)
+{
+ return name[0] == 0 || (name[0] == '/' && name[1] == 0);
+}
+
+#endif
diff --git a/src/db/Visitor.hxx b/src/db/Visitor.hxx
new file mode 100644
index 000000000..c524f1722
--- /dev/null
+++ b/src/db/Visitor.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_VISITOR_HXX
+#define MPD_DATABASE_VISITOR_HXX
+
+#include <functional>
+
+struct LightDirectory;
+struct LightSong;
+struct PlaylistInfo;
+struct Tag;
+class Error;
+
+typedef std::function<bool(const LightDirectory &, Error &)> VisitDirectory;
+typedef std::function<bool(const LightSong &, Error &)> VisitSong;
+typedef std::function<bool(const PlaylistInfo &, const LightDirectory &,
+ Error &)> VisitPlaylist;
+
+typedef std::function<bool(const Tag &, Error &)> VisitTag;
+
+#endif
diff --git a/src/db/plugins/LazyDatabase.cxx b/src/db/plugins/LazyDatabase.cxx
new file mode 100644
index 000000000..bc52395c5
--- /dev/null
+++ b/src/db/plugins/LazyDatabase.cxx
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LazyDatabase.hxx"
+#include "db/Interface.hxx"
+
+#include <assert.h>
+
+LazyDatabase::LazyDatabase(Database *_db)
+ :Database(_db->GetPlugin()), db(_db), open(false) {}
+
+LazyDatabase::~LazyDatabase()
+{
+ assert(!open);
+
+ delete db;
+}
+
+bool
+LazyDatabase::EnsureOpen(Error &error) const
+{
+ if (open)
+ return true;
+
+ if (!db->Open(error))
+ return false;
+
+ open = true;
+ return true;
+}
+
+void
+LazyDatabase::Close()
+{
+ if (open) {
+ open = false;
+ db->Close();
+ }
+}
+
+const LightSong *
+LazyDatabase::GetSong(const char *uri, Error &error) const
+{
+ return EnsureOpen(error)
+ ? db->GetSong(uri, error)
+ : nullptr;
+}
+
+void
+LazyDatabase::ReturnSong(const LightSong *song) const
+{
+ assert(open);
+
+ db->ReturnSong(song);
+}
+
+bool
+LazyDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ return EnsureOpen(error) &&
+ db->Visit(selection, visit_directory, visit_song,
+ visit_playlist, error);
+}
+
+bool
+LazyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const
+{
+ return EnsureOpen(error) &&
+ db->VisitUniqueTags(selection, tag_type, group_mask, visit_tag,
+ error);
+}
+
+bool
+LazyDatabase::GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats, Error &error) const
+{
+ return EnsureOpen(error) && db->GetStats(selection, stats, error);
+}
+
+time_t
+LazyDatabase::GetUpdateStamp() const
+{
+ return open ? db->GetUpdateStamp() : 0;
+}
diff --git a/src/db/plugins/LazyDatabase.hxx b/src/db/plugins/LazyDatabase.hxx
new file mode 100644
index 000000000..ae1b961d0
--- /dev/null
+++ b/src/db/plugins/LazyDatabase.hxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_LAZY_DATABASE_PLUGIN_HXX
+#define MPD_LAZY_DATABASE_PLUGIN_HXX
+
+#include "db/Interface.hxx"
+#include "Compiler.h"
+
+/**
+ * A wrapper for a #Database object that gets opened on the first
+ * database access. This works around daemonization problems with
+ * some plugins.
+ */
+class LazyDatabase final : public Database {
+ Database *const db;
+
+ mutable bool open;
+
+public:
+ gcc_nonnull_all
+ LazyDatabase(Database *_db);
+
+ virtual ~LazyDatabase();
+
+ virtual void Close() override;
+
+ virtual const LightSong *GetSong(const char *uri_utf8,
+ Error &error) const override;
+ virtual void ReturnSong(const LightSong *song) const;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const override;
+
+ virtual time_t GetUpdateStamp() const override;
+
+private:
+ bool EnsureOpen(Error &error) const;
+};
+
+#endif
diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx
new file mode 100644
index 000000000..5fd224bb5
--- /dev/null
+++ b/src/db/plugins/ProxyDatabasePlugin.cxx
@@ -0,0 +1,851 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ProxyDatabasePlugin.hxx"
+#include "db/Interface.hxx"
+#include "db/DatabasePlugin.hxx"
+#include "db/DatabaseListener.hxx"
+#include "db/Selection.hxx"
+#include "db/DatabaseError.hxx"
+#include "db/PlaylistInfo.hxx"
+#include "db/LightDirectory.hxx"
+#include "db/LightSong.hxx"
+#include "db/Stats.hxx"
+#include "SongFilter.hxx"
+#include "Compiler.h"
+#include "config/ConfigData.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "protocol/Ack.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/IdleMonitor.hxx"
+#include "Log.hxx"
+
+#include <mpd/client.h>
+#include <mpd/async.h>
+
+#include <cassert>
+#include <string>
+#include <list>
+
+class ProxySong : public LightSong {
+ Tag tag2;
+
+public:
+ explicit ProxySong(const mpd_song *song);
+};
+
+class AllocatedProxySong : public ProxySong {
+ mpd_song *const song;
+
+public:
+ explicit AllocatedProxySong(mpd_song *_song)
+ :ProxySong(_song), song(_song) {}
+
+ ~AllocatedProxySong() {
+ mpd_song_free(song);
+ }
+};
+
+class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
+ DatabaseListener &listener;
+
+ std::string host;
+ unsigned port;
+
+ struct mpd_connection *connection;
+
+ /* this is mutable because GetStats() must be "const" */
+ mutable time_t update_stamp;
+
+ /**
+ * The libmpdclient idle mask that was removed from the other
+ * MPD. This will be handled by the next OnIdle() call.
+ */
+ unsigned idle_received;
+
+ /**
+ * Is the #connection currently "idle"? That is, did we send
+ * the "idle" command to it?
+ */
+ bool is_idle;
+
+public:
+ ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener)
+ :Database(proxy_db_plugin),
+ SocketMonitor(_loop), IdleMonitor(_loop),
+ listener(_listener) {}
+
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
+ Error &error);
+
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual const LightSong *GetSong(const char *uri_utf8,
+ Error &error) const override;
+ void ReturnSong(const LightSong *song) const override;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const override;
+
+ virtual unsigned Update(const char *uri_utf8, bool discard,
+ Error &error) override;
+
+ virtual time_t GetUpdateStamp() const override {
+ return update_stamp;
+ }
+
+private:
+ bool Configure(const config_param &param, Error &error);
+
+ bool Connect(Error &error);
+ bool CheckConnection(Error &error);
+ bool EnsureConnected(Error &error);
+
+ void Disconnect();
+
+ /* virtual methods from SocketMonitor */
+ virtual bool OnSocketReady(unsigned flags) override;
+
+ /* virtual methods from IdleMonitor */
+ virtual void OnIdle() override;
+};
+
+static constexpr Domain libmpdclient_domain("libmpdclient");
+
+static constexpr struct {
+ TagType 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 },
+#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
+ { TAG_MUSICBRAINZ_RELEASETRACKID,
+ MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
+#endif
+ { TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
+};
+
+static void
+Copy(TagBuilder &tag, TagType 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 == nullptr)
+ break;
+
+ tag.AddItem(d_tag, value);
+ }
+}
+
+ProxySong::ProxySong(const mpd_song *song)
+{
+ directory = nullptr;
+ uri = mpd_song_get_uri(song);
+ real_uri = nullptr;
+ tag = &tag2;
+ mtime = mpd_song_get_last_modified(song);
+
+#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
+ start_time = SongTime::FromS(mpd_song_get_start(song));
+ end_time = SongTime::FromS(mpd_song_get_end(song));
+#else
+ start_time = end_time = SongTime::zero();
+#endif
+
+ TagBuilder tag_builder;
+
+ const unsigned duration = mpd_song_get_duration(song);
+ if (duration > 0)
+ tag_builder.SetDuration(SignedSongTime::FromS(duration));
+
+ for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
+ Copy(tag_builder, i->d, song, i->s);
+
+ tag_builder.Commit(tag2);
+}
+
+gcc_const
+static enum mpd_tag_type
+Convert(TagType 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, Error &error)
+{
+ const auto code = mpd_connection_get_error(connection);
+ if (code == MPD_ERROR_SUCCESS)
+ return true;
+
+ if (code == MPD_ERROR_SERVER) {
+ /* libmpdclient's "enum mpd_server_error" is the same
+ as our "enum ack" */
+ const auto server_error =
+ mpd_connection_get_server_error(connection);
+ error.Set(ack_domain, (int)server_error,
+ mpd_connection_get_error_message(connection));
+ } else {
+ error.Set(libmpdclient_domain, (int)code,
+ mpd_connection_get_error_message(connection));
+ }
+
+ mpd_connection_clear_error(connection);
+ return false;
+}
+
+static bool
+SendConstraints(mpd_connection *connection, const SongFilter::Item &item)
+{
+ switch (item.GetTag()) {
+ mpd_tag_type tag;
+
+#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
+ case LOCATE_TAG_BASE_TYPE:
+ if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0)
+ /* requires MPD 0.18 */
+ return true;
+
+ return mpd_search_add_base_constraint(connection,
+ MPD_OPERATOR_DEFAULT,
+ item.GetValue().c_str());
+#endif
+
+ case LOCATE_TAG_FILE_TYPE:
+ return mpd_search_add_uri_constraint(connection,
+ MPD_OPERATOR_DEFAULT,
+ item.GetValue().c_str());
+
+ case LOCATE_TAG_ANY_TYPE:
+ return mpd_search_add_any_tag_constraint(connection,
+ MPD_OPERATOR_DEFAULT,
+ item.GetValue().c_str());
+
+ default:
+ tag = Convert(TagType(item.GetTag()));
+ if (tag == MPD_TAG_COUNT)
+ return true;
+
+ return mpd_search_add_tag_constraint(connection,
+ MPD_OPERATOR_DEFAULT,
+ tag,
+ item.GetValue().c_str());
+ }
+}
+
+static bool
+SendConstraints(mpd_connection *connection, const SongFilter &filter)
+{
+ for (const auto &i : filter.GetItems())
+ if (!SendConstraints(connection, i))
+ return false;
+
+ return true;
+}
+
+static bool
+SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
+{
+#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
+ if (!selection.uri.empty() &&
+ mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) {
+ /* requires MPD 0.18 */
+ if (!mpd_search_add_base_constraint(connection,
+ MPD_OPERATOR_DEFAULT,
+ selection.uri.c_str()))
+ return false;
+ }
+#endif
+
+ if (selection.filter != nullptr &&
+ !SendConstraints(connection, *selection.filter))
+ return false;
+
+ return true;
+}
+
+Database *
+ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param, Error &error)
+{
+ ProxyDatabase *db = new ProxyDatabase(loop, listener);
+ if (!db->Configure(param, error)) {
+ delete db;
+ db = nullptr;
+ }
+
+ return db;
+}
+
+bool
+ProxyDatabase::Configure(const config_param &param, gcc_unused Error &error)
+{
+ host = param.GetBlockValue("host", "");
+ port = param.GetBlockValue("port", 0u);
+
+ return true;
+}
+
+bool
+ProxyDatabase::Open(Error &error)
+{
+ if (!Connect(error))
+ return false;
+
+ update_stamp = 0;
+
+ return true;
+}
+
+void
+ProxyDatabase::Close()
+{
+ if (connection != nullptr)
+ Disconnect();
+}
+
+bool
+ProxyDatabase::Connect(Error &error)
+{
+ const char *_host = host.empty() ? nullptr : host.c_str();
+ connection = mpd_connection_new(_host, port, 0);
+ if (connection == nullptr) {
+ error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM,
+ "Out of memory");
+ return false;
+ }
+
+ if (!CheckError(connection, error)) {
+ mpd_connection_free(connection);
+ connection = nullptr;
+
+ return false;
+ }
+
+ idle_received = unsigned(-1);
+ is_idle = false;
+
+ SocketMonitor::Open(mpd_async_get_fd(mpd_connection_get_async(connection)));
+ IdleMonitor::Schedule();
+
+ return true;
+}
+
+bool
+ProxyDatabase::CheckConnection(Error &error)
+{
+ assert(connection != nullptr);
+
+ if (!mpd_connection_clear_error(connection)) {
+ Disconnect();
+ return Connect(error);
+ }
+
+ if (is_idle) {
+ unsigned idle = mpd_run_noidle(connection);
+ if (idle == 0 && !CheckError(connection, error)) {
+ Disconnect();
+ return false;
+ }
+
+ idle_received |= idle;
+ is_idle = false;
+ IdleMonitor::Schedule();
+ }
+
+ return true;
+}
+
+bool
+ProxyDatabase::EnsureConnected(Error &error)
+{
+ return connection != nullptr
+ ? CheckConnection(error)
+ : Connect(error);
+}
+
+void
+ProxyDatabase::Disconnect()
+{
+ assert(connection != nullptr);
+
+ IdleMonitor::Cancel();
+ SocketMonitor::Steal();
+
+ mpd_connection_free(connection);
+ connection = nullptr;
+}
+
+bool
+ProxyDatabase::OnSocketReady(gcc_unused unsigned flags)
+{
+ assert(connection != nullptr);
+
+ if (!is_idle) {
+ // TODO: can this happen?
+ IdleMonitor::Schedule();
+ return false;
+ }
+
+ unsigned idle = (unsigned)mpd_recv_idle(connection, false);
+ if (idle == 0) {
+ Error error;
+ if (!CheckError(connection, error)) {
+ LogError(error);
+ Disconnect();
+ return false;
+ }
+ }
+
+ /* let OnIdle() handle this */
+ idle_received |= idle;
+ is_idle = false;
+ IdleMonitor::Schedule();
+ return false;
+}
+
+void
+ProxyDatabase::OnIdle()
+{
+ assert(connection != nullptr);
+
+ /* handle previous idle events */
+
+ if (idle_received & MPD_IDLE_DATABASE)
+ listener.OnDatabaseModified();
+
+ idle_received = 0;
+
+ /* send a new idle command to the other MPD */
+
+ if (is_idle)
+ // TODO: can this happen?
+ return;
+
+ if (!mpd_send_idle_mask(connection, MPD_IDLE_DATABASE)) {
+ Error error;
+ if (!CheckError(connection, error))
+ LogError(error);
+
+ SocketMonitor::Steal();
+ mpd_connection_free(connection);
+ connection = nullptr;
+ return;
+ }
+
+ is_idle = true;
+ SocketMonitor::ScheduleRead();
+}
+
+const LightSong *
+ProxyDatabase::GetSong(const char *uri, Error &error) const
+{
+ // TODO: eliminate the const_cast
+ if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
+ return nullptr;
+
+ if (!mpd_send_list_meta(connection, uri)) {
+ CheckError(connection, error);
+ return nullptr;
+ }
+
+ struct mpd_song *song = mpd_recv_song(connection);
+ if (!mpd_response_finish(connection) &&
+ !CheckError(connection, error)) {
+ if (song != nullptr)
+ mpd_song_free(song);
+ return nullptr;
+ }
+
+ if (song == nullptr) {
+ error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri);
+ return nullptr;
+ }
+
+ return new AllocatedProxySong(song);
+}
+
+void
+ProxyDatabase::ReturnSong(const LightSong *_song) const
+{
+ assert(_song != nullptr);
+
+ AllocatedProxySong *song = (AllocatedProxySong *)
+ const_cast<LightSong *>(_song);
+ delete song;
+}
+
+static bool
+Visit(struct mpd_connection *connection, const char *uri,
+ bool recursive, const SongFilter *filter,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist, Error &error);
+
+static bool
+Visit(struct mpd_connection *connection,
+ bool recursive, const SongFilter *filter,
+ const struct mpd_directory *directory,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist, Error &error)
+{
+ const char *path = mpd_directory_get_path(directory);
+#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
+ time_t mtime = mpd_directory_get_last_modified(directory);
+#else
+ time_t mtime = 0;
+#endif
+
+ if (visit_directory &&
+ !visit_directory(LightDirectory(path, mtime), error))
+ return false;
+
+ if (recursive &&
+ !Visit(connection, path, recursive, filter,
+ visit_directory, visit_song, visit_playlist, error))
+ return false;
+
+ return true;
+}
+
+gcc_pure
+static bool
+Match(const SongFilter *filter, const LightSong &song)
+{
+ return filter == nullptr || filter->Match(song);
+}
+
+static bool
+Visit(const SongFilter *filter,
+ const mpd_song *_song,
+ VisitSong visit_song, Error &error)
+{
+ if (!visit_song)
+ return true;
+
+ const ProxySong song(_song);
+ return !Match(filter, song) || visit_song(song, error);
+}
+
+static bool
+Visit(const struct mpd_playlist *playlist,
+ VisitPlaylist visit_playlist, Error &error)
+{
+ if (!visit_playlist)
+ return true;
+
+ PlaylistInfo p(mpd_playlist_get_path(playlist),
+ mpd_playlist_get_last_modified(playlist));
+
+ return visit_playlist(p, LightDirectory::Root(), error);
+}
+
+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)) != nullptr)
+ entities.push_back(ProxyEntity(entity));
+
+ mpd_response_finish(connection);
+ return entities;
+}
+
+static bool
+Visit(struct mpd_connection *connection, const char *uri,
+ bool recursive, const SongFilter *filter,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist, Error &error)
+{
+ if (!mpd_send_list_meta(connection, uri))
+ return CheckError(connection, error);
+
+ std::list<ProxyEntity> entities(ReceiveEntities(connection));
+ if (!CheckError(connection, error))
+ 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, filter,
+ mpd_entity_get_directory(entity),
+ visit_directory, visit_song, visit_playlist,
+ error))
+ return false;
+ break;
+
+ case MPD_ENTITY_TYPE_SONG:
+ if (!Visit(filter,
+ mpd_entity_get_song(entity), visit_song,
+ error))
+ return false;
+ break;
+
+ case MPD_ENTITY_TYPE_PLAYLIST:
+ if (!Visit(mpd_entity_get_playlist(entity),
+ visit_playlist, error))
+ return false;
+ break;
+ }
+ }
+
+ return CheckError(connection, error);
+}
+
+static bool
+SearchSongs(struct mpd_connection *connection,
+ const DatabaseSelection &selection,
+ VisitSong visit_song,
+ Error &error)
+{
+ assert(selection.recursive);
+ assert(visit_song);
+
+ const bool exact = selection.filter == nullptr ||
+ !selection.filter->HasFoldCase();
+
+ if (!mpd_search_db_songs(connection, exact) ||
+ !SendConstraints(connection, selection) ||
+ !mpd_search_commit(connection))
+ return CheckError(connection, error);
+
+ bool result = true;
+ struct mpd_song *song;
+ while (result && (song = mpd_recv_song(connection)) != nullptr) {
+ AllocatedProxySong song2(song);
+
+ result = !Match(selection.filter, song2) ||
+ visit_song(song2, error);
+ }
+
+ mpd_response_finish(connection);
+ return result && CheckError(connection, error);
+}
+
+/**
+ * Check whether we can use the "base" constraint. Requires
+ * libmpdclient 2.9 and MPD 0.18.
+ */
+gcc_pure
+static bool
+ServerSupportsSearchBase(const struct mpd_connection *connection)
+{
+#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
+ return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
+#else
+ (void)connection;
+
+ return false;
+#endif
+}
+
+bool
+ProxyDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ // TODO: eliminate the const_cast
+ if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
+ return false;
+
+ if (!visit_directory && !visit_playlist && selection.recursive &&
+ (ServerSupportsSearchBase(connection)
+ ? !selection.IsEmpty()
+ : selection.HasOtherThanBase()))
+ /* this optimized code path can only be used under
+ certain conditions */
+ return ::SearchSongs(connection, selection, visit_song, error);
+
+ /* fall back to recursive walk (slow!) */
+ return ::Visit(connection, selection.uri.c_str(),
+ selection.recursive, selection.filter,
+ visit_directory, visit_song, visit_playlist,
+ error);
+}
+
+bool
+ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type,
+ gcc_unused uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const
+{
+ // TODO: eliminate the const_cast
+ if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
+ return false;
+
+ enum mpd_tag_type tag_type2 = Convert(tag_type);
+ if (tag_type2 == MPD_TAG_COUNT) {
+ error.Set(libmpdclient_domain, "Unsupported tag");
+ return false;
+ }
+
+ if (!mpd_search_db_tags(connection, tag_type2))
+ return CheckError(connection, error);
+
+ if (!SendConstraints(connection, selection))
+ return CheckError(connection, error);
+
+ // TODO: use group_mask
+
+ if (!mpd_search_commit(connection))
+ return CheckError(connection, error);
+
+ bool result = true;
+
+ struct mpd_pair *pair;
+ while (result &&
+ (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) {
+ TagBuilder tag;
+ tag.AddItem(tag_type, pair->value);
+
+ if (tag.IsEmpty())
+ /* if no tag item has been added, then the
+ given value was not acceptable
+ (e.g. empty); forcefully insert an empty
+ tag in this case, as the caller expects the
+ given tag type to be present */
+ tag.AddEmptyItem(tag_type);
+
+ result = visit_tag(tag.Commit(), error);
+ mpd_return_pair(connection, pair);
+ }
+
+ return mpd_response_finish(connection) &&
+ CheckError(connection, error) &&
+ result;
+}
+
+bool
+ProxyDatabase::GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats, Error &error) const
+{
+ // TODO: match
+ (void)selection;
+
+ // TODO: eliminate the const_cast
+ if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
+ return false;
+
+ struct mpd_stats *stats2 =
+ mpd_run_stats(connection);
+ if (stats2 == nullptr)
+ return CheckError(connection, error);
+
+ update_stamp = (time_t)mpd_stats_get_db_update_time(stats2);
+
+ stats.song_count = mpd_stats_get_number_of_songs(stats2);
+ stats.total_duration = std::chrono::seconds(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;
+}
+
+unsigned
+ProxyDatabase::Update(const char *uri_utf8, bool discard,
+ Error &error)
+{
+ if (!EnsureConnected(error))
+ return 0;
+
+ unsigned id = discard
+ ? mpd_run_rescan(connection, uri_utf8)
+ : mpd_run_update(connection, uri_utf8);
+ if (id == 0)
+ CheckError(connection, error);
+
+ return id;
+}
+
+const DatabasePlugin proxy_db_plugin = {
+ "proxy",
+ DatabasePlugin::FLAG_REQUIRE_STORAGE,
+ ProxyDatabase::Create,
+};
diff --git a/src/db/plugins/ProxyDatabasePlugin.hxx b/src/db/plugins/ProxyDatabasePlugin.hxx
new file mode 100644
index 000000000..699d374b5
--- /dev/null
+++ b/src/db/plugins/ProxyDatabasePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PROXY_DATABASE_PLUGIN_HXX
+#define MPD_PROXY_DATABASE_PLUGIN_HXX
+
+struct DatabasePlugin;
+
+extern const DatabasePlugin proxy_db_plugin;
+
+#endif
diff --git a/src/db/plugins/simple/DatabaseSave.cxx b/src/db/plugins/simple/DatabaseSave.cxx
new file mode 100644
index 000000000..c766843b6
--- /dev/null
+++ b/src/db/plugins/simple/DatabaseSave.cxx
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DatabaseSave.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/DatabaseError.hxx"
+#include "Directory.hxx"
+#include "DirectorySave.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "fs/io/TextFile.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagSettings.h"
+#include "fs/Charset.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+#include <stdlib.h>
+
+#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: "
+
+static constexpr unsigned DB_FORMAT = 2;
+
+/**
+ * The oldest database format understood by this MPD version.
+ */
+static constexpr unsigned OLDEST_DB_FORMAT = 1;
+
+void
+db_save_internal(BufferedOutputStream &os, const Directory &music_root)
+{
+ os.Format("%s\n", DIRECTORY_INFO_BEGIN);
+ os.Format(DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
+ os.Format("%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
+ os.Format("%s%s\n", DIRECTORY_FS_CHARSET, GetFSCharset());
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (!ignore_tag_items[i])
+ os.Format(DB_TAG_PREFIX "%s\n", tag_item_names[i]);
+
+ os.Format("%s\n", DIRECTORY_INFO_END);
+
+ directory_save(os, music_root);
+}
+
+bool
+db_load_internal(TextFile &file, Directory &music_root, Error &error)
+{
+ char *line;
+ unsigned format = 0;
+ bool found_charset = false, found_version = false;
+ bool success;
+ bool tags[TAG_NUM_OF_ITEM_TYPES];
+
+ /* get initial info */
+ line = file.ReadLine();
+ if (line == nullptr || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
+ error.Set(db_domain, "Database corrupted");
+ return false;
+ }
+
+ memset(tags, false, sizeof(tags));
+
+ while ((line = file.ReadLine()) != nullptr &&
+ strcmp(line, DIRECTORY_INFO_END) != 0) {
+ if (StringStartsWith(line, DB_FORMAT_PREFIX)) {
+ format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
+ } else if (StringStartsWith(line, DIRECTORY_MPD_VERSION)) {
+ if (found_version) {
+ error.Set(db_domain, "Duplicate version line");
+ return false;
+ }
+
+ found_version = true;
+ } else if (StringStartsWith(line, DIRECTORY_FS_CHARSET)) {
+ const char *new_charset;
+
+ if (found_charset) {
+ error.Set(db_domain, "Duplicate charset line");
+ return false;
+ }
+
+ found_charset = true;
+
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
+ const char *const old_charset = GetFSCharset();
+ if (*old_charset != 0
+ && strcmp(new_charset, old_charset) != 0) {
+ error.Format(db_domain,
+ "Existing database has charset "
+ "\"%s\" instead of \"%s\"; "
+ "discarding database file",
+ new_charset, old_charset);
+ return false;
+ }
+ } else if (StringStartsWith(line, DB_TAG_PREFIX)) {
+ const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
+ TagType tag = tag_name_parse(name);
+ if (tag == TAG_NUM_OF_ITEM_TYPES) {
+ error.Format(db_domain,
+ "Unrecognized tag '%s', "
+ "discarding database file",
+ name);
+ return false;
+ }
+
+ tags[tag] = true;
+ } else {
+ error.Format(db_domain, "Malformed line: %s", line);
+ return false;
+ }
+ }
+
+ if (format < OLDEST_DB_FORMAT || format > DB_FORMAT) {
+ error.Set(db_domain,
+ "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]) {
+ error.Set(db_domain,
+ "Tag list mismatch, "
+ "discarding database file");
+ return false;
+ }
+ }
+
+ LogDebug(db_domain, "reading DB");
+
+ db_lock();
+ success = directory_load(file, music_root, error);
+ db_unlock();
+
+ return success;
+}
diff --git a/src/db/plugins/simple/DatabaseSave.hxx b/src/db/plugins/simple/DatabaseSave.hxx
new file mode 100644
index 000000000..bb7f57115
--- /dev/null
+++ b/src/db/plugins/simple/DatabaseSave.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DATABASE_SAVE_HXX
+#define MPD_DATABASE_SAVE_HXX
+
+struct Directory;
+class BufferedOutputStream;
+class TextFile;
+class Error;
+
+void
+db_save_internal(BufferedOutputStream &os, const Directory &root);
+
+bool
+db_load_internal(TextFile &file, Directory &root, Error &error);
+
+#endif
diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx
new file mode 100644
index 000000000..218652b03
--- /dev/null
+++ b/src/db/plugins/simple/Directory.cxx
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Directory.hxx"
+#include "SongSort.hxx"
+#include "Song.hxx"
+#include "Mount.hxx"
+#include "db/LightDirectory.hxx"
+#include "db/LightSong.hxx"
+#include "db/Uri.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/Interface.hxx"
+#include "SongFilter.hxx"
+#include "lib/icu/Collate.hxx"
+#include "fs/Traits.hxx"
+#include "util/Alloc.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+Directory::Directory(std::string &&_path_utf8, Directory *_parent)
+ :parent(_parent),
+ mtime(0),
+ inode(0), device(0),
+ path(std::move(_path_utf8)),
+ mounted_database(nullptr)
+{
+}
+
+Directory::~Directory()
+{
+ delete mounted_database;
+
+ songs.clear_and_dispose(Song::Disposer());
+ children.clear_and_dispose(Disposer());
+}
+
+void
+Directory::Delete()
+{
+ assert(holding_db_lock());
+ assert(parent != nullptr);
+
+ parent->children.erase_and_dispose(parent->children.iterator_to(*this),
+ Disposer());
+}
+
+const char *
+Directory::GetName() const
+{
+ assert(!IsRoot());
+
+ return PathTraitsUTF8::GetBase(path.c_str());
+}
+
+Directory *
+Directory::CreateChild(const char *name_utf8)
+{
+ assert(holding_db_lock());
+ assert(name_utf8 != nullptr);
+ assert(*name_utf8 != 0);
+
+ std::string path_utf8 = IsRoot()
+ ? std::string(name_utf8)
+ : PathTraitsUTF8::Build(GetPath(), name_utf8);
+
+ Directory *child = new Directory(std::move(path_utf8), this);
+ children.push_back(*child);
+ return child;
+}
+
+const Directory *
+Directory::FindChild(const char *name) const
+{
+ assert(holding_db_lock());
+
+ for (const auto &child : children)
+ if (strcmp(child.GetName(), name) == 0)
+ return &child;
+
+ return nullptr;
+}
+
+void
+Directory::PruneEmpty()
+{
+ assert(holding_db_lock());
+
+ for (auto child = children.begin(), end = children.end();
+ child != end;) {
+ child->PruneEmpty();
+
+ if (child->IsEmpty())
+ child = children.erase_and_dispose(child, Disposer());
+ else
+ ++child;
+ }
+}
+
+Directory::LookupResult
+Directory::LookupDirectory(const char *uri)
+{
+ assert(holding_db_lock());
+ assert(uri != nullptr);
+
+ if (isRootDirectory(uri))
+ return { this, nullptr };
+
+ char *duplicated = xstrdup(uri), *name = duplicated;
+
+ Directory *d = this;
+ while (true) {
+ char *slash = strchr(name, '/');
+ if (slash == name)
+ break;
+
+ if (slash != nullptr)
+ *slash = '\0';
+
+ Directory *tmp = d->FindChild(name);
+ if (tmp == nullptr)
+ /* not found */
+ break;
+
+ d = tmp;
+
+ if (slash == nullptr) {
+ /* found everything */
+ name = nullptr;
+ break;
+ }
+
+ name = slash + 1;
+ }
+
+ free(duplicated);
+
+ const char *rest = name == nullptr
+ ? nullptr
+ : uri + (name - duplicated);
+
+ return { d, rest };
+}
+
+void
+Directory::AddSong(Song *song)
+{
+ assert(holding_db_lock());
+ assert(song != nullptr);
+ assert(song->parent == this);
+
+ songs.push_back(*song);
+}
+
+void
+Directory::RemoveSong(Song *song)
+{
+ assert(holding_db_lock());
+ assert(song != nullptr);
+ assert(song->parent == this);
+
+ songs.erase(songs.iterator_to(*song));
+}
+
+const Song *
+Directory::FindSong(const char *name_utf8) const
+{
+ assert(holding_db_lock());
+ assert(name_utf8 != nullptr);
+
+ for (auto &song : songs) {
+ assert(song.parent == this);
+
+ if (strcmp(song.uri, name_utf8) == 0)
+ return &song;
+ }
+
+ return nullptr;
+}
+
+gcc_pure
+static bool
+directory_cmp(const Directory &a, const Directory &b)
+{
+ return IcuCollate(a.path.c_str(), b.path.c_str()) < 0;
+}
+
+void
+Directory::Sort()
+{
+ assert(holding_db_lock());
+
+ children.sort(directory_cmp);
+ song_list_sort(songs);
+
+ for (auto &child : children)
+ child.Sort();
+}
+
+bool
+Directory::Walk(bool recursive, const SongFilter *filter,
+ VisitDirectory visit_directory, VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ assert(!error.IsDefined());
+
+ if (IsMount()) {
+ assert(IsEmpty());
+
+ /* TODO: eliminate this unlock/lock; it is necessary
+ because the child's SimpleDatabasePlugin::Visit()
+ call will lock it again */
+ db_unlock();
+ bool result = WalkMount(GetPath(), *mounted_database,
+ recursive, filter,
+ visit_directory, visit_song,
+ visit_playlist,
+ error);
+ db_lock();
+ return result;
+ }
+
+ if (visit_song) {
+ for (auto &song : songs){
+ const LightSong song2 = song.Export();
+ if ((filter == nullptr || filter->Match(song2)) &&
+ !visit_song(song2, error))
+ return false;
+ }
+ }
+
+ if (visit_playlist) {
+ for (const PlaylistInfo &p : playlists)
+ if (!visit_playlist(p, Export(), error))
+ return false;
+ }
+
+ for (auto &child : children) {
+ if (visit_directory &&
+ !visit_directory(child.Export(), error))
+ return false;
+
+ if (recursive &&
+ !child.Walk(recursive, filter,
+ visit_directory, visit_song, visit_playlist,
+ error))
+ return false;
+ }
+
+ return true;
+}
+
+LightDirectory
+Directory::Export() const
+{
+ return LightDirectory(GetPath(), mtime);
+}
diff --git a/src/db/plugins/simple/Directory.hxx b/src/db/plugins/simple/Directory.hxx
new file mode 100644
index 000000000..acef62143
--- /dev/null
+++ b/src/db/plugins/simple/Directory.hxx
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DIRECTORY_HXX
+#define MPD_DIRECTORY_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "db/Visitor.hxx"
+#include "db/PlaylistVector.hxx"
+#include "Song.hxx"
+
+#include <boost/intrusive/list.hpp>
+
+#include <string>
+
+/**
+ * Virtual directory that is really an archive file or a folder inside
+ * the archive (special value for Directory::device).
+ */
+static constexpr unsigned DEVICE_INARCHIVE = -1;
+
+/**
+ * Virtual directory that is really a song file with one or more "sub"
+ * songs as specified by DecoderPlugin::container_scan() (special
+ * value for Directory::device).
+ */
+static constexpr unsigned DEVICE_CONTAINER = -2;
+
+struct db_visitor;
+class SongFilter;
+class Error;
+class Database;
+
+struct Directory {
+ static constexpr auto link_mode = boost::intrusive::normal_link;
+ typedef boost::intrusive::link_mode<link_mode> LinkMode;
+ typedef boost::intrusive::list_member_hook<LinkMode> Hook;
+
+ struct Disposer {
+ void operator()(Directory *directory) const {
+ delete 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.
+ */
+ Hook siblings;
+
+ typedef boost::intrusive::member_hook<Directory, Hook,
+ &Directory::siblings> SiblingsHook;
+ typedef boost::intrusive::list<Directory, SiblingsHook,
+ boost::intrusive::constant_time_size<false>> List;
+
+ /**
+ * 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.
+ */
+ List 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.
+ */
+ SongList songs;
+
+ PlaylistVector playlists;
+
+ Directory *parent;
+ time_t mtime;
+ unsigned inode, device;
+
+ std::string path;
+
+ /**
+ * If this is not nullptr, then this directory does not really
+ * exist, but is a mount point for another #Database.
+ */
+ Database *mounted_database;
+
+public:
+ Directory(std::string &&_path_utf8, Directory *_parent);
+ ~Directory();
+
+ /**
+ * Create a new root #Directory object.
+ */
+ gcc_malloc
+ static Directory *NewRoot() {
+ return new Directory(std::string(), nullptr);
+ }
+
+ bool IsMount() const {
+ return mounted_database != nullptr;
+ }
+
+ /**
+ * 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;
+ }
+
+ struct LookupResult {
+ /**
+ * The last directory that was found. If the given
+ * URI could not be resolved at all, then this is the
+ * root directory.
+ */
+ Directory *directory;
+
+ /**
+ * The remaining URI part (without leading slash) or
+ * nullptr if the given URI was consumed completely.
+ */
+ const char *uri;
+ };
+
+ /**
+ * Looks up a directory by its relative URI.
+ *
+ * @param uri the relative URI
+ * @return the Directory, or nullptr if none was found
+ */
+ gcc_pure
+ LookupResult LookupDirectory(const char *uri);
+
+ gcc_pure
+ bool IsEmpty() const {
+ return children.empty() &&
+ songs.empty() &&
+ playlists.empty();
+ }
+
+ gcc_pure
+ const char *GetPath() const {
+ return path.c_str();
+ }
+
+ /**
+ * 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 == nullptr;
+ }
+
+ template<typename T>
+ void ForEachChildSafe(T &&t) {
+ const auto end = children.end();
+ for (auto i = children.begin(), next = i; i != end; i = next) {
+ next = std::next(i);
+ t(*i);
+ }
+ }
+
+ template<typename T>
+ void ForEachSongSafe(T &&t) {
+ const auto end = songs.end();
+ for (auto i = songs.begin(), next = i; i != end; i = next) {
+ next = std::next(i);
+ t(*i);
+ }
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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,
+ Error &error) const;
+
+ gcc_pure
+ LightDirectory Export() const;
+};
+
+#endif
diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
new file mode 100644
index 000000000..e1650cbe8
--- /dev/null
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DirectorySave.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "SongSave.hxx"
+#include "DetachedSong.hxx"
+#include "PlaylistDatabase.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "util/StringUtil.hxx"
+#include "util/NumberParser.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <stddef.h>
+#include <string.h>
+
+#define DIRECTORY_DIR "directory: "
+#define DIRECTORY_TYPE "type: "
+#define DIRECTORY_MTIME "mtime: "
+#define DIRECTORY_BEGIN "begin: "
+#define DIRECTORY_END "end: "
+
+static constexpr Domain directory_domain("directory");
+
+gcc_const
+static const char *
+DeviceToTypeString(unsigned device)
+{
+ switch (device) {
+ case DEVICE_INARCHIVE:
+ return "archive";
+
+ case DEVICE_CONTAINER:
+ return "container";
+
+ default:
+ return nullptr;
+ }
+}
+
+gcc_pure
+static unsigned
+ParseTypeString(const char *type)
+{
+ if (strcmp(type, "archive") == 0)
+ return DEVICE_INARCHIVE;
+ else if (strcmp(type, "container") == 0)
+ return DEVICE_CONTAINER;
+ else
+ return 0;
+}
+
+void
+directory_save(BufferedOutputStream &os, const Directory &directory)
+{
+ if (!directory.IsRoot()) {
+ const char *type = DeviceToTypeString(directory.device);
+ if (type != nullptr)
+ os.Format(DIRECTORY_TYPE "%s\n", type);
+
+ if (directory.mtime != 0)
+ os.Format(DIRECTORY_MTIME "%lu\n",
+ (unsigned long)directory.mtime);
+
+ os.Format("%s%s\n", DIRECTORY_BEGIN, directory.GetPath());
+ }
+
+ for (const auto &child : directory.children) {
+ os.Format(DIRECTORY_DIR "%s\n", child.GetName());
+
+ if (!child.IsMount())
+ directory_save(os, child);
+
+ if (!os.Check())
+ return;
+ }
+
+ for (const auto &song : directory.songs)
+ song_save(os, song);
+
+ playlist_vector_save(os, directory.playlists);
+
+ if (!directory.IsRoot())
+ os.Format(DIRECTORY_END "%s\n", directory.GetPath());
+}
+
+static bool
+ParseLine(Directory &directory, const char *line)
+{
+ if (StringStartsWith(line, DIRECTORY_MTIME)) {
+ directory.mtime =
+ ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1);
+ } else if (StringStartsWith(line, DIRECTORY_TYPE)) {
+ directory.device =
+ ParseTypeString(line + sizeof(DIRECTORY_TYPE) - 1);
+ } else
+ return false;
+
+ return true;
+}
+
+static Directory *
+directory_load_subdir(TextFile &file, Directory &parent, const char *name,
+ Error &error)
+{
+ bool success;
+
+ if (parent.FindChild(name) != nullptr) {
+ error.Format(directory_domain,
+ "Duplicate subdirectory '%s'", name);
+ return nullptr;
+ }
+
+ Directory *directory = parent.CreateChild(name);
+
+ while (true) {
+ const char *line = file.ReadLine();
+ if (line == nullptr) {
+ error.Set(directory_domain, "Unexpected end of file");
+ directory->Delete();
+ return nullptr;
+ }
+
+ if (StringStartsWith(line, DIRECTORY_BEGIN))
+ break;
+
+ if (!ParseLine(*directory, line)) {
+ error.Format(directory_domain,
+ "Malformed line: %s", line);
+ directory->Delete();
+ return nullptr;
+ }
+ }
+
+ success = directory_load(file, *directory, error);
+ if (!success) {
+ directory->Delete();
+ return nullptr;
+ }
+
+ return directory;
+}
+
+bool
+directory_load(TextFile &file, Directory &directory, Error &error)
+{
+ const char *line;
+
+ while ((line = file.ReadLine()) != nullptr &&
+ !StringStartsWith(line, DIRECTORY_END)) {
+ if (StringStartsWith(line, DIRECTORY_DIR)) {
+ Directory *subdir =
+ directory_load_subdir(file, directory,
+ line + sizeof(DIRECTORY_DIR) - 1,
+ error);
+ if (subdir == nullptr)
+ return false;
+ } else if (StringStartsWith(line, SONG_BEGIN)) {
+ const char *name = line + sizeof(SONG_BEGIN) - 1;
+
+ if (directory.FindSong(name) != nullptr) {
+ error.Format(directory_domain,
+ "Duplicate song '%s'", name);
+ return false;
+ }
+
+ DetachedSong *song = song_load(file, name, error);
+ if (song == nullptr)
+ return false;
+
+ directory.AddSong(Song::NewFrom(std::move(*song),
+ directory));
+ delete song;
+ } else if (StringStartsWith(line, PLAYLIST_META_BEGIN)) {
+ const char *name = line + sizeof(PLAYLIST_META_BEGIN) - 1;
+ if (!playlist_metadata_load(file, directory.playlists,
+ name, error))
+ return false;
+ } else {
+ error.Format(directory_domain,
+ "Malformed line: %s", line);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/db/plugins/simple/DirectorySave.hxx b/src/db/plugins/simple/DirectorySave.hxx
new file mode 100644
index 000000000..f464f9946
--- /dev/null
+++ b/src/db/plugins/simple/DirectorySave.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DIRECTORY_SAVE_HXX
+#define MPD_DIRECTORY_SAVE_HXX
+
+struct Directory;
+class TextFile;
+class BufferedOutputStream;
+class Error;
+
+void
+directory_save(BufferedOutputStream &os, const Directory &directory);
+
+bool
+directory_load(TextFile &file, Directory &directory, Error &error);
+
+#endif
diff --git a/src/db/plugins/simple/Mount.cxx b/src/db/plugins/simple/Mount.cxx
new file mode 100644
index 000000000..96c7bbb5c
--- /dev/null
+++ b/src/db/plugins/simple/Mount.cxx
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Mount.hxx"
+#include "PrefixedLightSong.hxx"
+#include "db/Selection.hxx"
+#include "db/LightDirectory.hxx"
+#include "db/LightSong.hxx"
+#include "db/Interface.hxx"
+#include "fs/Traits.hxx"
+#include "util/Error.hxx"
+
+#include <string>
+
+struct PrefixedLightDirectory : LightDirectory {
+ std::string buffer;
+
+ PrefixedLightDirectory(const LightDirectory &directory,
+ const char *base)
+ :LightDirectory(directory),
+ buffer(IsRoot()
+ ? std::string(base)
+ : PathTraitsUTF8::Build(base, uri)) {
+ uri = buffer.c_str();
+ }
+};
+
+static bool
+PrefixVisitDirectory(const char *base, const VisitDirectory &visit_directory,
+ const LightDirectory &directory, Error &error)
+{
+ return visit_directory(PrefixedLightDirectory(directory, base), error);
+}
+
+static bool
+PrefixVisitSong(const char *base, const VisitSong &visit_song,
+ const LightSong &song, Error &error)
+{
+ return visit_song(PrefixedLightSong(song, base), error);
+}
+
+static bool
+PrefixVisitPlaylist(const char *base, const VisitPlaylist &visit_playlist,
+ const PlaylistInfo &playlist,
+ const LightDirectory &directory,
+ Error &error)
+{
+ return visit_playlist(playlist,
+ PrefixedLightDirectory(directory, base),
+ error);
+}
+
+bool
+WalkMount(const char *base, const Database &db,
+ bool recursive, const SongFilter *filter,
+ const VisitDirectory &visit_directory, const VisitSong &visit_song,
+ const VisitPlaylist &visit_playlist,
+ Error &error)
+{
+ using namespace std::placeholders;
+
+ VisitDirectory vd;
+ if (visit_directory)
+ vd = std::bind(PrefixVisitDirectory,
+ base, std::ref(visit_directory), _1, _2);
+
+ VisitSong vs;
+ if (visit_song)
+ vs = std::bind(PrefixVisitSong,
+ base, std::ref(visit_song), _1, _2);
+
+ VisitPlaylist vp;
+ if (visit_playlist)
+ vp = std::bind(PrefixVisitPlaylist,
+ base, std::ref(visit_playlist), _1, _2, _3);
+
+ return db.Visit(DatabaseSelection("", recursive, filter),
+ vd, vs, vp, error);
+}
diff --git a/src/db/plugins/simple/Mount.hxx b/src/db/plugins/simple/Mount.hxx
new file mode 100644
index 000000000..a4690114c
--- /dev/null
+++ b/src/db/plugins/simple/Mount.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_SIMPLE_MOUNT_HXX
+#define MPD_DB_SIMPLE_MOUNT_HXX
+
+#include "db/Visitor.hxx"
+
+class Database;
+class SongFilter;
+class Error;
+
+bool
+WalkMount(const char *base, const Database &db,
+ bool recursive, const SongFilter *filter,
+ const VisitDirectory &visit_directory, const VisitSong &visit_song,
+ const VisitPlaylist &visit_playlist,
+ Error &error);
+
+#endif
diff --git a/src/db/plugins/simple/PrefixedLightSong.hxx b/src/db/plugins/simple/PrefixedLightSong.hxx
new file mode 100644
index 000000000..3664de001
--- /dev/null
+++ b/src/db/plugins/simple/PrefixedLightSong.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DB_SIMPLE_PREFIXED_LIGHT_SONG_HXX
+#define MPD_DB_SIMPLE_PREFIXED_LIGHT_SONG_HXX
+
+#include "check.h"
+#include "db/LightSong.hxx"
+#include "fs/Traits.hxx"
+
+#include <string>
+
+class PrefixedLightSong : public LightSong {
+ std::string buffer;
+
+public:
+ PrefixedLightSong(const LightSong &song, const char *base)
+ :LightSong(song),
+ buffer(PathTraitsUTF8::Build(base, GetURI().c_str())) {
+ uri = buffer.c_str();
+ directory = nullptr;
+ }
+};
+
+#endif
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
new file mode 100644
index 000000000..7b1886f1c
--- /dev/null
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SimpleDatabasePlugin.hxx"
+#include "PrefixedLightSong.hxx"
+#include "db/DatabasePlugin.hxx"
+#include "db/Selection.hxx"
+#include "db/Helpers.hxx"
+#include "db/UniqueTags.hxx"
+#include "db/LightDirectory.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "SongFilter.hxx"
+#include "DatabaseSave.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/DatabaseError.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "fs/io/FileOutputStream.hxx"
+#include "config/ConfigData.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/CharUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#ifdef HAVE_ZLIB
+#include "fs/io/GzipOutputStream.hxx"
+#endif
+
+#include <errno.h>
+
+static constexpr Domain simple_db_domain("simple_db");
+
+inline SimpleDatabase::SimpleDatabase()
+ :Database(simple_db_plugin),
+ path(AllocatedPath::Null()),
+#ifdef HAVE_ZLIB
+ compress(true),
+#endif
+ cache_path(AllocatedPath::Null()),
+ prefixed_light_song(nullptr) {}
+
+inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path,
+#ifndef HAVE_ZLIB
+ gcc_unused
+#endif
+ bool _compress)
+ :Database(simple_db_plugin),
+ path(std::move(_path)),
+ path_utf8(path.ToUTF8()),
+#ifdef HAVE_ZLIB
+ compress(_compress),
+#endif
+ cache_path(AllocatedPath::Null()),
+ prefixed_light_song(nullptr) {
+}
+
+Database *
+SimpleDatabase::Create(gcc_unused EventLoop &loop,
+ gcc_unused DatabaseListener &listener,
+ const config_param &param, Error &error)
+{
+ SimpleDatabase *db = new SimpleDatabase();
+ if (!db->Configure(param, error)) {
+ delete db;
+ db = nullptr;
+ }
+
+ return db;
+}
+
+bool
+SimpleDatabase::Configure(const config_param &param, Error &error)
+{
+ path = param.GetBlockPath("path", error);
+ if (path.IsNull()) {
+ if (!error.IsDefined())
+ error.Set(simple_db_domain,
+ "No \"path\" parameter specified");
+ return false;
+ }
+
+ path_utf8 = path.ToUTF8();
+
+ cache_path = param.GetBlockPath("cache_directory", error);
+ if (path.IsNull() && error.IsDefined())
+ return false;
+
+#ifdef HAVE_ZLIB
+ compress = param.GetBlockValue("compress", compress);
+#endif
+
+ return true;
+}
+
+bool
+SimpleDatabase::Check(Error &error) const
+{
+ assert(!path.IsNull());
+
+ /* Check if the file exists */
+ if (!CheckAccess(path)) {
+ /* If the file doesn't exist, we can't check if we can write
+ * it, so we are going to try to get the directory path, and
+ * see if we can write a file in that */
+ const auto dirPath = path.GetDirectoryName();
+
+ /* Check that the parent part of the path is a directory */
+ struct stat st;
+ if (!StatFile(dirPath, st)) {
+ error.FormatErrno("Couldn't stat parent directory of db file "
+ "\"%s\"",
+ path_utf8.c_str());
+ return false;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ error.Format(simple_db_domain,
+ "Couldn't create db file \"%s\" because the "
+ "parent path is not a directory",
+ path_utf8.c_str());
+ return false;
+ }
+
+#ifndef WIN32
+ /* Check if we can write to the directory */
+ if (!CheckAccess(dirPath, X_OK | W_OK)) {
+ const int e = errno;
+ const std::string dirPath_utf8 = dirPath.ToUTF8();
+ error.FormatErrno(e, "Can't create db file in \"%s\"",
+ dirPath_utf8.c_str());
+ return false;
+ }
+#endif
+ return true;
+ }
+
+ /* Path exists, now check if it's a regular file */
+ struct stat st;
+ if (!StatFile(path, st)) {
+ error.FormatErrno("Couldn't stat db file \"%s\"",
+ path_utf8.c_str());
+ return false;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ error.Format(simple_db_domain,
+ "db file \"%s\" is not a regular file",
+ path_utf8.c_str());
+ return false;
+ }
+
+#ifndef WIN32
+ /* And check that we can write to it */
+ if (!CheckAccess(path, R_OK | W_OK)) {
+ error.FormatErrno("Can't open db file \"%s\" for reading/writing",
+ path_utf8.c_str());
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+bool
+SimpleDatabase::Load(Error &error)
+{
+ assert(!path.IsNull());
+ assert(root != nullptr);
+
+ TextFile file(path, error);
+ if (file.HasFailed())
+ return false;
+
+ if (!db_load_internal(file, *root, error) || !file.Check(error))
+ return false;
+
+ struct stat st;
+ if (StatFile(path, st))
+ mtime = st.st_mtime;
+
+ return true;
+}
+
+bool
+SimpleDatabase::Open(Error &error)
+{
+ assert(prefixed_light_song == nullptr);
+
+ root = Directory::NewRoot();
+ mtime = 0;
+
+#ifndef NDEBUG
+ borrowed_song_count = 0;
+#endif
+
+ if (!Load(error)) {
+ delete root;
+
+ LogError(error);
+ error.Clear();
+
+ if (!Check(error))
+ return false;
+
+ root = Directory::NewRoot();
+ }
+
+ return true;
+}
+
+void
+SimpleDatabase::Close()
+{
+ assert(root != nullptr);
+ assert(prefixed_light_song == nullptr);
+ assert(borrowed_song_count == 0);
+
+ delete root;
+}
+
+const LightSong *
+SimpleDatabase::GetSong(const char *uri, Error &error) const
+{
+ assert(root != nullptr);
+ assert(prefixed_light_song == nullptr);
+ assert(borrowed_song_count == 0);
+
+ db_lock();
+
+ auto r = root->LookupDirectory(uri);
+
+ if (r.directory->IsMount()) {
+ /* pass the request to the mounted database */
+ db_unlock();
+
+ const LightSong *song =
+ r.directory->mounted_database->GetSong(r.uri, error);
+ if (song == nullptr)
+ return nullptr;
+
+ prefixed_light_song =
+ new PrefixedLightSong(*song, r.directory->GetPath());
+ return prefixed_light_song;
+ }
+
+ if (r.uri == nullptr) {
+ /* it's a directory */
+ db_unlock();
+ error.Format(db_domain, DB_NOT_FOUND,
+ "No such song: %s", uri);
+ return nullptr;
+ }
+
+ if (strchr(r.uri, '/') != nullptr) {
+ /* refers to a URI "below" the actual song */
+ db_unlock();
+ error.Format(db_domain, DB_NOT_FOUND,
+ "No such song: %s", uri);
+ return nullptr;
+ }
+
+ const Song *song = r.directory->FindSong(r.uri);
+ db_unlock();
+ if (song == nullptr) {
+ error.Format(db_domain, DB_NOT_FOUND,
+ "No such song: %s", uri);
+ return nullptr;
+ }
+
+ light_song = song->Export();
+
+#ifndef NDEBUG
+ ++borrowed_song_count;
+#endif
+
+ return &light_song;
+}
+
+void
+SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const
+{
+ assert(song != nullptr);
+ assert(song == &light_song || song == prefixed_light_song);
+
+ delete prefixed_light_song;
+ prefixed_light_song = nullptr;
+
+#ifndef NDEBUG
+ if (song == &light_song) {
+ assert(borrowed_song_count > 0);
+ --borrowed_song_count;
+ }
+#endif
+}
+
+bool
+SimpleDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ ScopeDatabaseLock protect;
+
+ auto r = root->LookupDirectory(selection.uri.c_str());
+ if (r.uri == nullptr) {
+ /* it's a directory */
+
+ if (selection.recursive && visit_directory &&
+ !visit_directory(r.directory->Export(), error))
+ return false;
+
+ return r.directory->Walk(selection.recursive, selection.filter,
+ visit_directory, visit_song,
+ visit_playlist,
+ error);
+ }
+
+ if (strchr(r.uri, '/') == nullptr) {
+ if (visit_song) {
+ Song *song = r.directory->FindSong(r.uri);
+ if (song != nullptr) {
+ const LightSong song2 = song->Export();
+ return !selection.Match(song2) ||
+ visit_song(song2, error);
+ }
+ }
+ }
+
+ error.Set(db_domain, DB_NOT_FOUND, "No such directory");
+ return false;
+}
+
+bool
+SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const
+{
+ return ::VisitUniqueTags(*this, selection, tag_type, group_mask,
+ visit_tag,
+ error);
+}
+
+bool
+SimpleDatabase::GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats, Error &error) const
+{
+ return ::GetStats(*this, selection, stats, error);
+}
+
+bool
+SimpleDatabase::Save(Error &error)
+{
+ db_lock();
+
+ LogDebug(simple_db_domain, "removing empty directories from DB");
+ root->PruneEmpty();
+
+ LogDebug(simple_db_domain, "sorting DB");
+ root->Sort();
+
+ db_unlock();
+
+ LogDebug(simple_db_domain, "writing DB");
+
+ FileOutputStream fos(path, error);
+ if (!fos.IsDefined())
+ return false;
+
+ OutputStream *os = &fos;
+
+#ifdef HAVE_ZLIB
+ GzipOutputStream *gzip = nullptr;
+ if (compress) {
+ gzip = new GzipOutputStream(*os, error);
+ if (!gzip->IsDefined()) {
+ delete gzip;
+ return false;
+ }
+
+ os = gzip;
+ }
+#endif
+
+ BufferedOutputStream bos(*os);
+
+ db_save_internal(bos, *root);
+
+ if (!bos.Flush(error)) {
+#ifdef HAVE_ZLIB
+ delete gzip;
+#endif
+ return false;
+ }
+
+#ifdef HAVE_ZLIB
+ if (gzip != nullptr) {
+ bool success = gzip->Flush(error);
+ delete gzip;
+ if (!success)
+ return false;
+ }
+#endif
+
+ if (!fos.Commit(error))
+ return false;
+
+ struct stat st;
+ if (StatFile(path, st))
+ mtime = st.st_mtime;
+
+ return true;
+}
+
+bool
+SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
+{
+ assert(uri != nullptr);
+ assert(*uri != 0);
+ assert(db != nullptr);
+
+ ScopeDatabaseLock protect;
+
+ auto r = root->LookupDirectory(uri);
+ if (r.uri == nullptr) {
+ error.Format(db_domain, DB_CONFLICT,
+ "Already exists: %s", uri);
+ return nullptr;
+ }
+
+ if (strchr(r.uri, '/') != nullptr) {
+ error.Format(db_domain, DB_NOT_FOUND,
+ "Parent not found: %s", uri);
+ return nullptr;
+ }
+
+ Directory *mnt = r.directory->CreateChild(r.uri);
+ mnt->mounted_database = db;
+ return true;
+}
+
+static constexpr bool
+IsSafeChar(char ch)
+{
+ return IsAlphaNumericASCII(ch) || ch == '-' || ch == '_' || ch == '%';
+}
+
+static constexpr bool
+IsUnsafeChar(char ch)
+{
+ return !IsSafeChar(ch);
+}
+
+bool
+SimpleDatabase::Mount(const char *local_uri, const char *storage_uri,
+ Error &error)
+{
+ if (cache_path.IsNull()) {
+ error.Format(db_domain, DB_NOT_FOUND,
+ "No 'cache_directory' configured");
+ return nullptr;
+ }
+
+ std::string name(storage_uri);
+ std::replace_if(name.begin(), name.end(), IsUnsafeChar, '_');
+
+#ifndef HAVE_ZLIB
+ constexpr bool compress = false;
+#endif
+ auto db = new SimpleDatabase(AllocatedPath::Build(cache_path,
+ name.c_str()),
+ compress);
+ if (!db->Open(error)) {
+ delete db;
+ return false;
+ }
+
+ // TODO: update the new database instance?
+
+ if (!Mount(local_uri, db, error)) {
+ db->Close();
+ delete db;
+ return false;
+ }
+
+ return true;
+}
+
+Database *
+SimpleDatabase::LockUmountSteal(const char *uri)
+{
+ ScopeDatabaseLock protect;
+
+ auto r = root->LookupDirectory(uri);
+ if (r.uri != nullptr || !r.directory->IsMount())
+ return nullptr;
+
+ Database *db = r.directory->mounted_database;
+ r.directory->mounted_database = nullptr;
+ r.directory->Delete();
+
+ return db;
+}
+
+bool
+SimpleDatabase::Unmount(const char *uri)
+{
+ Database *db = LockUmountSteal(uri);
+ if (db == nullptr)
+ return false;
+
+ db->Close();
+ delete db;
+ return true;
+}
+
+const DatabasePlugin simple_db_plugin = {
+ "simple",
+ DatabasePlugin::FLAG_REQUIRE_STORAGE,
+ SimpleDatabase::Create,
+};
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx
new file mode 100644
index 000000000..d82225f8c
--- /dev/null
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SIMPLE_DATABASE_PLUGIN_HXX
+#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
+
+#include "check.h"
+#include "db/Interface.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "db/LightSong.hxx"
+#include "Compiler.h"
+
+#include <cassert>
+
+struct config_param;
+struct Directory;
+struct DatabasePlugin;
+class EventLoop;
+class DatabaseListener;
+class PrefixedLightSong;
+
+class SimpleDatabase : public Database {
+ AllocatedPath path;
+ std::string path_utf8;
+
+#ifdef HAVE_ZLIB
+ bool compress;
+#endif
+
+ /**
+ * The path where cache files for Mount() are located.
+ */
+ AllocatedPath cache_path;
+
+ Directory *root;
+
+ time_t mtime;
+
+ /**
+ * A buffer for GetSong() when prefixing the #LightSong
+ * instance from a mounted #Database.
+ */
+ mutable PrefixedLightSong *prefixed_light_song;
+
+ /**
+ * A buffer for GetSong().
+ */
+ mutable LightSong light_song;
+
+#ifndef NDEBUG
+ mutable unsigned borrowed_song_count;
+#endif
+
+ SimpleDatabase();
+
+ SimpleDatabase(AllocatedPath &&_path, bool _compress);
+
+public:
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
+ Error &error);
+
+ gcc_pure
+ Directory &GetRoot() {
+ assert(root != NULL);
+
+ return *root;
+ }
+
+ bool Save(Error &error);
+
+ /**
+ * Returns true if there is a valid database file on the disk.
+ */
+ bool FileExists() const {
+ return mtime > 0;
+ }
+
+ /**
+ * @param db the #Database to be mounted; must be "open"; on
+ * success, this object gains ownership of the given #Database
+ */
+ gcc_nonnull_all
+ bool Mount(const char *uri, Database *db, Error &error);
+
+ gcc_nonnull_all
+ bool Mount(const char *local_uri, const char *storage_uri,
+ Error &error);
+
+ gcc_nonnull_all
+ bool Unmount(const char *uri);
+
+ /* virtual methods from class Database */
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+
+ const LightSong *GetSong(const char *uri_utf8,
+ Error &error) const override;
+ void ReturnSong(const LightSong *song) const override;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const override;
+
+ virtual time_t GetUpdateStamp() const override {
+ return mtime;
+ }
+
+private:
+ bool Configure(const config_param &param, Error &error);
+
+ gcc_pure
+ bool Check(Error &error) const;
+
+ bool Load(Error &error);
+
+ Database *LockUmountSteal(const char *uri);
+};
+
+extern const DatabasePlugin simple_db_plugin;
+
+#endif
diff --git a/src/db/plugins/simple/Song.cxx b/src/db/plugins/simple/Song.cxx
new file mode 100644
index 000000000..fbfc2ec19
--- /dev/null
+++ b/src/db/plugins/simple/Song.cxx
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Song.hxx"
+#include "Directory.hxx"
+#include "tag/Tag.hxx"
+#include "util/VarSize.hxx"
+#include "DetachedSong.hxx"
+#include "db/LightSong.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+inline Song::Song(const char *_uri, size_t uri_length, Directory &_parent)
+ :parent(&_parent), mtime(0),
+ start_time(SongTime::zero()), end_time(SongTime::zero())
+{
+ memcpy(uri, _uri, uri_length + 1);
+}
+
+inline Song::~Song()
+{
+}
+
+static Song *
+song_alloc(const char *uri, Directory &parent)
+{
+ size_t uri_length;
+
+ assert(uri);
+ uri_length = strlen(uri);
+ assert(uri_length);
+
+ return NewVarSize<Song>(sizeof(Song::uri),
+ uri_length + 1,
+ uri, uri_length, parent);
+}
+
+Song *
+Song::NewFrom(DetachedSong &&other, Directory &parent)
+{
+ Song *song = song_alloc(other.GetURI(), parent);
+ song->tag = std::move(other.WritableTag());
+ song->mtime = other.GetLastModified();
+ song->start_time = other.GetStartTime();
+ song->end_time = other.GetEndTime();
+ return song;
+}
+
+Song *
+Song::NewFile(const char *path, Directory &parent)
+{
+ return song_alloc(path, parent);
+}
+
+void
+Song::Free()
+{
+ DeleteVarSize(this);
+}
+
+std::string
+Song::GetURI() const
+{
+ assert(*uri);
+
+ if (parent->IsRoot())
+ return std::string(uri);
+ else {
+ const char *path = parent->GetPath();
+
+ std::string result;
+ result.reserve(strlen(path) + 1 + strlen(uri));
+ result.assign(path);
+ result.push_back('/');
+ result.append(uri);
+ return result;
+ }
+}
+
+LightSong
+Song::Export() const
+{
+ LightSong dest;
+ dest.directory = parent->IsRoot()
+ ? nullptr : parent->GetPath();
+ dest.uri = uri;
+ dest.real_uri = nullptr;
+ dest.tag = &tag;
+ dest.mtime = mtime;
+ dest.start_time = start_time;
+ dest.end_time = end_time;
+ return dest;
+}
diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx
new file mode 100644
index 000000000..9f3a4a3ef
--- /dev/null
+++ b/src/db/plugins/simple/Song.hxx
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SONG_HXX
+#define MPD_SONG_HXX
+
+#include "Chrono.hxx"
+#include "tag/Tag.hxx"
+#include "Compiler.h"
+
+#include <boost/intrusive/list.hpp>
+
+#include <string>
+
+#include <assert.h>
+#include <time.h>
+
+struct LightSong;
+struct Directory;
+class DetachedSong;
+class Storage;
+
+/**
+ * A song file inside the configured music directory. Internal
+ * #SimpleDatabase class.
+ */
+struct Song {
+ static constexpr auto link_mode = boost::intrusive::normal_link;
+ typedef boost::intrusive::link_mode<link_mode> LinkMode;
+ typedef boost::intrusive::list_member_hook<LinkMode> Hook;
+
+ struct Disposer {
+ void operator()(Song *song) const {
+ song->Free();
+ }
+ };
+
+ /**
+ * 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.
+ */
+ Hook siblings;
+
+ Tag tag;
+
+ /**
+ * The #Directory that contains this song. Must be
+ * non-nullptr. directory this way.
+ */
+ Directory *const parent;
+
+ time_t mtime;
+
+ /**
+ * Start of this sub-song within the file.
+ */
+ SongTime start_time;
+
+ /**
+ * End of this sub-song within the file.
+ * Unused if zero.
+ */
+ SongTime end_time;
+
+ /**
+ * The file name.
+ */
+ char uri[sizeof(int)];
+
+ Song(const char *_uri, size_t uri_length, Directory &parent);
+ ~Song();
+
+ gcc_malloc
+ static Song *NewFrom(DetachedSong &&other, Directory &parent);
+
+ /** 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(Storage &storage, const char *name_utf8,
+ Directory &parent);
+
+ void Free();
+
+ bool UpdateFile(Storage &storage);
+ bool UpdateFileInArchive(const Storage &storage);
+
+ /**
+ * Returns the URI of the song in UTF-8 encoding, including its
+ * location within the music directory.
+ */
+ gcc_pure
+ std::string GetURI() const;
+
+ gcc_pure
+ LightSong Export() const;
+};
+
+typedef boost::intrusive::list<Song,
+ boost::intrusive::member_hook<Song, Song::Hook,
+ &Song::siblings>,
+ boost::intrusive::constant_time_size<false>> SongList;
+
+#endif
diff --git a/src/db/plugins/simple/SongSort.cxx b/src/db/plugins/simple/SongSort.cxx
new file mode 100644
index 000000000..4b7144937
--- /dev/null
+++ b/src/db/plugins/simple/SongSort.cxx
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SongSort.hxx"
+#include "Song.hxx"
+#include "tag/Tag.hxx"
+#include "lib/icu/Collate.hxx"
+
+#include <stdlib.h>
+
+static int
+compare_utf8_string(const char *a, const char *b)
+{
+ if (a == nullptr)
+ return b == nullptr ? 0 : -1;
+
+ if (b == nullptr)
+ return 1;
+
+ return IcuCollate(a, b);
+}
+
+/**
+ * Compare two string tag values, ignoring case. Either one may be
+ * nullptr.
+ */
+static int
+compare_string_tag_item(const Tag &a, const Tag &b,
+ TagType type)
+{
+ return compare_utf8_string(a.GetValue(type),
+ b.GetValue(type));
+}
+
+/**
+ * Compare two tag values which should contain an integer value
+ * (e.g. disc or track number). Either one may be nullptr.
+ */
+static int
+compare_number_string(const char *a, const char *b)
+{
+ long ai = a == nullptr ? 0 : strtol(a, nullptr, 10);
+ long bi = b == nullptr ? 0 : strtol(b, nullptr, 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, TagType type)
+{
+ return compare_number_string(a.GetValue(type),
+ b.GetValue(type));
+}
+
+/* Only used for sorting/searchin a songvec, not general purpose compares */
+gcc_pure
+static bool
+song_cmp(const Song &a, 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 < 0;
+
+ /* then sort by disc */
+ ret = compare_tag_item(a.tag, b.tag, TAG_DISC);
+ if (ret != 0)
+ return ret < 0;
+
+ /* then by track number */
+ ret = compare_tag_item(a.tag, b.tag, TAG_TRACK);
+ if (ret != 0)
+ return ret < 0;
+
+ /* still no difference? compare file name */
+ return IcuCollate(a.uri, b.uri) < 0;
+}
+
+void
+song_list_sort(SongList &songs)
+{
+ songs.sort(song_cmp);
+}
diff --git a/src/db/plugins/simple/SongSort.hxx b/src/db/plugins/simple/SongSort.hxx
new file mode 100644
index 000000000..2a0c4383b
--- /dev/null
+++ b/src/db/plugins/simple/SongSort.hxx
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SONG_SORT_HXX
+#define MPD_SONG_SORT_HXX
+
+#include "Song.hxx"
+
+struct list_head;
+
+void
+song_list_sort(SongList &songs);
+
+#endif
diff --git a/src/db/plugins/upnp/ContentDirectoryService.cxx b/src/db/plugins/upnp/ContentDirectoryService.cxx
new file mode 100644
index 000000000..88d4bd644
--- /dev/null
+++ b/src/db/plugins/upnp/ContentDirectoryService.cxx
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "lib/upnp/ContentDirectoryService.hxx"
+#include "lib/upnp/Domain.hxx"
+#include "lib/upnp/ixmlwrap.hxx"
+#include "lib/upnp/Action.hxx"
+#include "Directory.hxx"
+#include "util/NumberParser.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+
+#include <stdio.h>
+
+static bool
+ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response, Error &error)
+{
+ const char *p = ixmlwrap::getFirstElementValue(response, "Result");
+ if (p == nullptr)
+ p = "";
+
+ return dirbuf.parse(p, error);
+}
+
+inline bool
+ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
+ const char *objectId, unsigned offset,
+ unsigned count, UPnPDirContent &dirbuf,
+ unsigned &didreadp, unsigned &totalp,
+ Error &error) const
+{
+ // Create request
+ char ofbuf[100], cntbuf[100];
+ sprintf(ofbuf, "%u", offset);
+ sprintf(cntbuf, "%u", count);
+ // Some devices require an empty SortCriteria, else bad params
+ IXML_Document *request =
+ MakeActionHelper("Browse", m_serviceType.c_str(),
+ "ObjectID", objectId,
+ "BrowseFlag", "BrowseDirectChildren",
+ "Filter", "*",
+ "SortCriteria", "",
+ "StartingIndex", ofbuf,
+ "RequestedCount", cntbuf);
+ if (request == nullptr) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ IXML_Document *response;
+ int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ ixmlDocument_free(request);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ const char *value = ixmlwrap::getFirstElementValue(response, "NumberReturned");
+ didreadp = value != nullptr
+ ? ParseUnsigned(value)
+ : 0;
+
+ value = ixmlwrap::getFirstElementValue(response, "TotalMatches");
+ if (value != nullptr)
+ totalp = ParseUnsigned(value);
+
+ bool success = ReadResultTag(dirbuf, response, error);
+ ixmlDocument_free(response);
+ return success;
+}
+
+bool
+ContentDirectoryService::readDir(UpnpClient_Handle handle,
+ const char *objectId,
+ UPnPDirContent &dirbuf,
+ Error &error) const
+{
+ unsigned offset = 0, total = -1, count;
+
+ do {
+ if (!readDirSlice(handle, objectId, offset, m_rdreqcnt, dirbuf,
+ count, total, error))
+ return false;
+
+ offset += count;
+ } while (count > 0 && offset < total);
+
+ return true;
+}
+
+bool
+ContentDirectoryService::search(UpnpClient_Handle hdl,
+ const char *objectId,
+ const char *ss,
+ UPnPDirContent &dirbuf,
+ Error &error) const
+{
+ unsigned offset = 0, total = -1, count;
+
+ do {
+ char ofbuf[100];
+ sprintf(ofbuf, "%d", offset);
+
+ IXML_Document *request =
+ MakeActionHelper("Search", m_serviceType.c_str(),
+ "ContainerID", objectId,
+ "SearchCriteria", ss,
+ "Filter", "*",
+ "SortCriteria", "",
+ "StartingIndex", ofbuf,
+ "RequestedCount", "0"); // Setting a value here gets twonky into fits
+ if (request == 0) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ IXML_Document *response;
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ ixmlDocument_free(request);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ const char *value =
+ ixmlwrap::getFirstElementValue(response, "NumberReturned");
+ count = value != nullptr
+ ? ParseUnsigned(value)
+ : 0;
+
+ offset += count;
+
+ value = ixmlwrap::getFirstElementValue(response, "TotalMatches");
+ if (value != nullptr)
+ total = ParseUnsigned(value);
+
+ bool success = ReadResultTag(dirbuf, response, error);
+ ixmlDocument_free(response);
+ if (!success)
+ return false;
+ } while (count > 0 && offset < total);
+
+ return true;
+}
+
+bool
+ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
+ const char *objectId,
+ UPnPDirContent &dirbuf,
+ Error &error) const
+{
+ // Create request
+ IXML_Document *request =
+ MakeActionHelper("Browse", m_serviceType.c_str(),
+ "ObjectID", objectId,
+ "BrowseFlag", "BrowseMetadata",
+ "Filter", "*",
+ "SortCriteria", "",
+ "StartingIndex", "0",
+ "RequestedCount", "1");
+ if (request == nullptr) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ IXML_Document *response;
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ ixmlDocument_free(request);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ bool success = ReadResultTag(dirbuf, response, error);
+ ixmlDocument_free(response);
+ return success;
+}
diff --git a/src/db/plugins/upnp/Directory.cxx b/src/db/plugins/upnp/Directory.cxx
new file mode 100644
index 000000000..e94a1a997
--- /dev/null
+++ b/src/db/plugins/upnp/Directory.cxx
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Directory.hxx"
+#include "lib/upnp/Util.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Tags.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/TagTable.hxx"
+#include "util/NumberParser.hxx"
+
+#include <algorithm>
+#include <string>
+
+#include <string.h>
+
+UPnPDirContent::~UPnPDirContent()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+gcc_pure gcc_nonnull_all
+static bool
+CompareStringLiteral(const char *literal, const char *value, size_t length)
+{
+ return length == strlen(literal) &&
+ memcmp(literal, value, length) == 0;
+}
+
+gcc_pure
+static UPnPDirObject::ItemClass
+ParseItemClass(const char *name, size_t length)
+{
+ if (CompareStringLiteral("object.item.audioItem.musicTrack",
+ name, length))
+ return UPnPDirObject::ItemClass::MUSIC;
+ else if (CompareStringLiteral("object.item.playlistItem",
+ name, length))
+ return UPnPDirObject::ItemClass::PLAYLIST;
+ else
+ return UPnPDirObject::ItemClass::UNKNOWN;
+}
+
+gcc_pure
+static SignedSongTime
+ParseDuration(const char *duration)
+{
+ char *endptr;
+
+ unsigned result = ParseUnsigned(duration, &endptr);
+ if (endptr == duration || *endptr != ':')
+ return SignedSongTime::Negative();
+
+ result *= 60;
+ duration = endptr + 1;
+ result += ParseUnsigned(duration, &endptr);
+ if (endptr == duration || *endptr != ':')
+ return SignedSongTime::Negative();
+
+ result *= 60;
+ duration = endptr + 1;
+ result += ParseUnsigned(duration, &endptr);
+ if (endptr == duration || *endptr != 0)
+ return SignedSongTime::Negative();
+
+ return SignedSongTime::FromS(result);
+}
+
+/**
+ * Transform titles to turn '/' into '_' to make them acceptable path
+ * elements. There is a very slight risk of collision in doing
+ * this. Twonky returns directory names (titles) like 'Artist/Album'.
+ */
+gcc_pure
+static std::string
+titleToPathElt(std::string &&s)
+{
+ std::replace(s.begin(), s.end(), '/', '_');
+ return s;
+}
+
+/**
+ * An XML parser which builds directory contents from DIDL lite input.
+ */
+class UPnPDirParser final : public CommonExpatParser {
+ UPnPDirContent &m_dir;
+
+ enum {
+ NONE,
+ RES,
+ CLASS,
+ } state;
+
+ /**
+ * If not equal to #TAG_NUM_OF_ITEM_TYPES, then we're
+ * currently reading an element containing a tag value. The
+ * value is being constructed in #value.
+ */
+ TagType tag_type;
+
+ /**
+ * The text inside the current element.
+ */
+ std::string value;
+
+ UPnPDirObject m_tobj;
+ TagBuilder tag;
+
+public:
+ UPnPDirParser(UPnPDirContent& dir)
+ :m_dir(dir),
+ state(NONE),
+ tag_type(TAG_NUM_OF_ITEM_TYPES)
+ {
+ m_tobj.clear();
+ }
+
+protected:
+ virtual void StartElement(const XML_Char *name, const XML_Char **attrs)
+ {
+ if (m_tobj.type != UPnPDirObject::Type::UNKNOWN &&
+ tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ tag_type = tag_table_lookup(upnp_tags, name);
+ if (tag_type != TAG_NUM_OF_ITEM_TYPES)
+ return;
+ } else {
+ assert(tag_type == TAG_NUM_OF_ITEM_TYPES);
+ }
+
+ switch (name[0]) {
+ case 'c':
+ if (!strcmp(name, "container")) {
+ m_tobj.clear();
+ m_tobj.type = UPnPDirObject::Type::CONTAINER;
+
+ const char *id = GetAttribute(attrs, "id");
+ if (id != nullptr)
+ m_tobj.m_id = id;
+
+ const char *pid = GetAttribute(attrs, "parentID");
+ if (pid != nullptr)
+ m_tobj.m_pid = pid;
+ }
+ break;
+
+ case 'i':
+ if (!strcmp(name, "item")) {
+ m_tobj.clear();
+ m_tobj.type = UPnPDirObject::Type::ITEM;
+
+ const char *id = GetAttribute(attrs, "id");
+ if (id != nullptr)
+ m_tobj.m_id = id;
+
+ const char *pid = GetAttribute(attrs, "parentID");
+ if (pid != nullptr)
+ m_tobj.m_pid = pid;
+ }
+ break;
+
+ case 'r':
+ if (!strcmp(name, "res")) {
+ // <res protocolInfo="http-get:*:audio/mpeg:*" size="5171496"
+ // bitrate="24576" duration="00:03:35" sampleFrequency="44100"
+ // nrAudioChannels="2">
+
+ const char *duration =
+ GetAttribute(attrs, "duration");
+ if (duration != nullptr)
+ tag.SetDuration(ParseDuration(duration));
+
+ state = RES;
+ }
+
+ break;
+
+ case 'u':
+ if (strcmp(name, "upnp:class") == 0)
+ state = CLASS;
+ }
+ }
+
+ bool checkobjok() {
+ if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() ||
+ m_tobj.name.empty() ||
+ (m_tobj.type == UPnPDirObject::Type::ITEM &&
+ m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN))
+ return false;
+
+ return true;
+ }
+
+ virtual void EndElement(const XML_Char *name)
+ {
+ if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
+ assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN);
+
+ tag.AddItem(tag_type, value.c_str());
+
+ if (tag_type == TAG_TITLE)
+ m_tobj.name = titleToPathElt(std::move(value));
+
+ value.clear();
+ tag_type = TAG_NUM_OF_ITEM_TYPES;
+ return;
+ }
+
+ if ((!strcmp(name, "container") || !strcmp(name, "item")) &&
+ checkobjok()) {
+ tag.Commit(m_tobj.tag);
+ m_dir.objects.emplace_back(std::move(m_tobj));
+ }
+
+ state = NONE;
+ }
+
+ virtual void CharacterData(const XML_Char *s, int len)
+ {
+ if (tag_type != TAG_NUM_OF_ITEM_TYPES) {
+ assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN);
+
+ value.append(s, len);
+ return;
+ }
+
+ switch (state) {
+ case NONE:
+ break;
+
+ case RES:
+ m_tobj.url.assign(s, len);
+ break;
+
+ case CLASS:
+ m_tobj.item_class = ParseItemClass(s, len);
+ break;
+ }
+ }
+};
+
+bool
+UPnPDirContent::parse(const char *input, Error &error)
+{
+ UPnPDirParser parser(*this);
+ return parser.Parse(input, strlen(input), true, error);
+}
diff --git a/src/db/plugins/upnp/Directory.hxx b/src/db/plugins/upnp/Directory.hxx
new file mode 100644
index 000000000..433979900
--- /dev/null
+++ b/src/db/plugins/upnp/Directory.hxx
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_DIRECTORY_HXX
+#define MPD_UPNP_DIRECTORY_HXX
+
+#include "Object.hxx"
+#include "Compiler.h"
+
+#include <string>
+#include <vector>
+
+class Error;
+
+/**
+ * Image of a MediaServer Directory Service container (directory),
+ * possibly containing items and subordinate containers.
+ */
+class UPnPDirContent {
+public:
+ std::vector<UPnPDirObject> objects;
+
+ ~UPnPDirContent();
+
+ gcc_pure
+ UPnPDirObject *FindObject(const char *name) {
+ for (auto &o : objects)
+ if (o.name == name)
+ return &o;
+
+ return nullptr;
+ }
+
+ /**
+ * Parse from DIDL-Lite XML data.
+ *
+ * Normally only used by ContentDirectoryService::readDir()
+ * This is cumulative: in general, the XML data is obtained in
+ * several documents corresponding to (offset,count) slices of the
+ * directory (container). parse() can be called repeatedly with
+ * the successive XML documents and will accumulate entries in the item
+ * and container vectors. This makes more sense if the different
+ * chunks are from the same container, but given that UPnP Ids are
+ * actually global, nothing really bad will happen if you mix
+ * up...
+ */
+ bool parse(const char *didltext, Error &error);
+};
+
+#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */
diff --git a/src/db/plugins/upnp/Object.cxx b/src/db/plugins/upnp/Object.cxx
new file mode 100644
index 000000000..703fb0be4
--- /dev/null
+++ b/src/db/plugins/upnp/Object.cxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Object.hxx"
+
+UPnPDirObject::~UPnPDirObject()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
diff --git a/src/db/plugins/upnp/Object.hxx b/src/db/plugins/upnp/Object.hxx
new file mode 100644
index 000000000..16a66c774
--- /dev/null
+++ b/src/db/plugins/upnp/Object.hxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_OBJECT_HXX
+#define MPD_UPNP_OBJECT_HXX
+
+#include "tag/Tag.hxx"
+
+#include <string>
+
+/**
+ * UpnP Media Server directory entry, converted from XML data.
+ *
+ * This is a dumb data holder class, a struct with helpers.
+ */
+class UPnPDirObject {
+public:
+ enum class Type {
+ UNKNOWN,
+ ITEM,
+ CONTAINER,
+ };
+
+ // There are actually several kinds of containers:
+ // object.container.storageFolder, object.container.person,
+ // object.container.playlistContainer etc., but they all seem to
+ // behave the same as far as we're concerned. Otoh, musicTrack
+ // items are special to us, and so should playlists, but I've not
+ // seen one of the latter yet (servers seem to use containers for
+ // playlists).
+ enum class ItemClass {
+ UNKNOWN,
+ MUSIC,
+ PLAYLIST,
+ };
+
+ std::string m_id; // ObjectId
+ std::string m_pid; // Parent ObjectId
+ std::string url;
+
+ /**
+ * A copy of "dc:title" sanitized as a file name.
+ */
+ std::string name;
+
+ Type type;
+ ItemClass item_class;
+
+ Tag tag;
+
+ UPnPDirObject() = default;
+ UPnPDirObject(UPnPDirObject &&) = default;
+
+ ~UPnPDirObject();
+
+ UPnPDirObject &operator=(UPnPDirObject &&) = default;
+
+ void clear()
+ {
+ m_id.clear();
+ m_pid.clear();
+ url.clear();
+ type = Type::UNKNOWN;
+ item_class = ItemClass::UNKNOWN;
+ tag.Clear();
+ }
+};
+
+#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */
diff --git a/src/db/plugins/upnp/Tags.cxx b/src/db/plugins/upnp/Tags.cxx
new file mode 100644
index 000000000..fd65df4d0
--- /dev/null
+++ b/src/db/plugins/upnp/Tags.cxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Tags.hxx"
+#include "tag/TagTable.hxx"
+
+const struct tag_table upnp_tags[] = {
+ { "upnp:artist", TAG_ARTIST },
+ { "upnp:album", TAG_ALBUM },
+ { "upnp:originalTrackNumber", TAG_TRACK },
+ { "upnp:genre", TAG_GENRE },
+ { "dc:title", TAG_TITLE },
+
+ /* sentinel */
+ { nullptr, TAG_NUM_OF_ITEM_TYPES }
+};
diff --git a/src/db/plugins/upnp/Tags.hxx b/src/db/plugins/upnp/Tags.hxx
new file mode 100644
index 000000000..ec6d18478
--- /dev/null
+++ b/src/db/plugins/upnp/Tags.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_TAGS_HXX
+#define MPD_UPNP_TAGS_HXX
+
+/**
+ * Map UPnP property names to MPD tags.
+ */
+extern const struct tag_table upnp_tags[];
+
+#endif
diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
new file mode 100644
index 000000000..21ddb8790
--- /dev/null
+++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
@@ -0,0 +1,783 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "UpnpDatabasePlugin.hxx"
+#include "Directory.hxx"
+#include "Tags.hxx"
+#include "lib/upnp/Domain.hxx"
+#include "lib/upnp/ClientInit.hxx"
+#include "lib/upnp/Discovery.hxx"
+#include "lib/upnp/ContentDirectoryService.hxx"
+#include "lib/upnp/Util.hxx"
+#include "db/Interface.hxx"
+#include "db/DatabasePlugin.hxx"
+#include "db/Selection.hxx"
+#include "db/DatabaseError.hxx"
+#include "db/LightDirectory.hxx"
+#include "db/LightSong.hxx"
+#include "db/Stats.hxx"
+#include "config/ConfigData.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/TagTable.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "fs/Traits.hxx"
+#include "Log.hxx"
+#include "SongFilter.hxx"
+
+#include <string>
+#include <vector>
+#include <set>
+
+#include <assert.h>
+#include <string.h>
+
+static const char *const rootid = "0";
+
+class UpnpSong : public LightSong {
+ std::string uri2, real_uri2;
+
+ Tag tag2;
+
+public:
+ UpnpSong(UPnPDirObject &&object, std::string &&_uri)
+ :uri2(std::move(_uri)),
+ real_uri2(std::move(object.url)),
+ tag2(std::move(object.tag)) {
+ directory = nullptr;
+ uri = uri2.c_str();
+ real_uri = real_uri2.c_str();
+ tag = &tag2;
+ mtime = 0;
+ start_time = end_time = SongTime::zero();
+ }
+};
+
+class UpnpDatabase : public Database {
+ UpnpClient_Handle handle;
+ UPnPDeviceDirectory *discovery;
+
+public:
+ UpnpDatabase():Database(upnp_db_plugin) {}
+
+ static Database *Create(EventLoop &loop, DatabaseListener &listener,
+ const config_param &param,
+ Error &error);
+
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual const LightSong *GetSong(const char *uri_utf8,
+ Error &error) const override;
+ virtual void ReturnSong(const LightSong *song) const;
+
+ virtual bool Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const override;
+
+ virtual bool VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag_type, uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const override;
+
+ virtual bool GetStats(const DatabaseSelection &selection,
+ DatabaseStats &stats,
+ Error &error) const override;
+ virtual time_t GetUpdateStamp() const {return 0;}
+
+protected:
+ bool Configure(const config_param &param, Error &error);
+
+private:
+ bool VisitServer(const ContentDirectoryService &server,
+ const std::list<std::string> &vpath,
+ const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const;
+
+ /**
+ * Run an UPnP search according to MPD parameters, and
+ * visit_song the results.
+ */
+ bool SearchSongs(const ContentDirectoryService &server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ VisitSong visit_song,
+ Error &error) const;
+
+ bool SearchSongs(const ContentDirectoryService &server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ UPnPDirContent& dirbuf,
+ Error &error) const;
+
+ bool Namei(const ContentDirectoryService &server,
+ const std::list<std::string> &vpath,
+ UPnPDirObject &dirent,
+ Error &error) const;
+
+ /**
+ * Take server and objid, return metadata.
+ */
+ bool ReadNode(const ContentDirectoryService &server,
+ const char *objid, UPnPDirObject& dirent,
+ Error &error) const;
+
+ /**
+ * Get the path for an object Id. This works much like pwd,
+ * except easier cause our inodes have a parent id. Not used
+ * any more actually (see comments in SearchSongs).
+ */
+ bool BuildPath(const ContentDirectoryService &server,
+ const UPnPDirObject& dirent, std::string &idpath,
+ Error &error) const;
+};
+
+Database *
+UpnpDatabase::Create(gcc_unused EventLoop &loop,
+ gcc_unused DatabaseListener &listener,
+ const config_param &param, Error &error)
+{
+ UpnpDatabase *db = new UpnpDatabase();
+ if (!db->Configure(param, error)) {
+ delete db;
+ return nullptr;
+ }
+
+ /* libupnp loses its ability to receive multicast messages
+ apparently due to daemonization; using the LazyDatabase
+ wrapper works around this problem */
+ return db;
+}
+
+inline bool
+UpnpDatabase::Configure(const config_param &, Error &)
+{
+ return true;
+}
+
+bool
+UpnpDatabase::Open(Error &error)
+{
+ if (!UpnpClientGlobalInit(handle, error))
+ return false;
+
+ discovery = new UPnPDeviceDirectory(handle);
+ if (!discovery->Start(error)) {
+ delete discovery;
+ UpnpClientGlobalFinish();
+ return false;
+ }
+
+ return true;
+}
+
+void
+UpnpDatabase::Close()
+{
+ delete discovery;
+ UpnpClientGlobalFinish();
+}
+
+void
+UpnpDatabase::ReturnSong(const LightSong *_song) const
+{
+ assert(_song != nullptr);
+
+ UpnpSong *song = (UpnpSong *)const_cast<LightSong *>(_song);
+ delete song;
+}
+
+// Get song info by path. We can receive either the id path, or the titles
+// one
+const LightSong *
+UpnpDatabase::GetSong(const char *uri, Error &error) const
+{
+ auto vpath = stringToTokens(uri, "/", true);
+ if (vpath.size() < 2) {
+ error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri);
+ return nullptr;
+ }
+
+ ContentDirectoryService server;
+ if (!discovery->getServer(vpath.front().c_str(), server, error))
+ return nullptr;
+
+ vpath.pop_front();
+
+ UPnPDirObject dirent;
+ if (vpath.front() != rootid) {
+ if (!Namei(server, vpath, dirent, error))
+ return nullptr;
+ } else {
+ if (!ReadNode(server, vpath.back().c_str(), dirent,
+ error))
+ return nullptr;
+ }
+
+ return new UpnpSong(std::move(dirent), uri);
+}
+
+/**
+ * Double-quote a string, adding internal backslash escaping.
+ */
+static void
+dquote(std::string &out, const char *in)
+{
+ out.push_back('"');
+
+ for (; *in != 0; ++in) {
+ switch(*in) {
+ case '\\':
+ case '"':
+ out.push_back('\\');
+ break;
+ }
+
+ out.push_back(*in);
+ }
+
+ out.push_back('"');
+}
+
+// Run an UPnP search, according to MPD parameters. Return results as
+// UPnP items
+bool
+UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ UPnPDirContent &dirbuf,
+ Error &error) const
+{
+ const SongFilter *filter = selection.filter;
+ if (selection.filter == nullptr)
+ return true;
+
+ std::list<std::string> searchcaps;
+ if (!server.getSearchCapabilities(handle, searchcaps, error))
+ return false;
+
+ if (searchcaps.empty())
+ return true;
+
+ std::string cond;
+ for (const auto &item : filter->GetItems()) {
+ switch (auto tag = item.GetTag()) {
+ case LOCATE_TAG_ANY_TYPE:
+ {
+ if (!cond.empty()) {
+ cond += " and ";
+ }
+ cond += '(';
+ bool first(true);
+ for (const auto& cap : searchcaps) {
+ if (first)
+ first = false;
+ else
+ cond += " or ";
+ cond += cap;
+ if (item.GetFoldCase()) {
+ cond += " contains ";
+ } else {
+ cond += " = ";
+ }
+ dquote(cond, item.GetValue().c_str());
+ }
+ cond += ')';
+ }
+ break;
+
+ default:
+ /* Unhandled conditions like
+ LOCATE_TAG_BASE_TYPE or
+ LOCATE_TAG_FILE_TYPE won't have a
+ corresponding upnp prop, so they will be
+ skipped */
+ if (tag == TAG_ALBUM_ARTIST)
+ tag = TAG_ARTIST;
+
+ // TODO: support LOCATE_TAG_ANY_TYPE etc.
+ const char *name = tag_table_lookup(upnp_tags,
+ TagType(tag));
+ if (name == nullptr)
+ continue;
+
+ if (!cond.empty()) {
+ cond += " and ";
+ }
+ cond += name;
+
+ /* FoldCase doubles up as contains/equal
+ switch. UpNP search is supposed to be
+ case-insensitive, but at least some servers
+ have the same convention as mpd (e.g.:
+ minidlna) */
+ if (item.GetFoldCase()) {
+ cond += " contains ";
+ } else {
+ cond += " = ";
+ }
+ dquote(cond, item.GetValue().c_str());
+ }
+ }
+
+ return server.search(handle,
+ objid, cond.c_str(), dirbuf,
+ error);
+}
+
+static bool
+visitSong(const UPnPDirObject &meta, const char *path,
+ const DatabaseSelection &selection,
+ VisitSong visit_song, Error& error)
+{
+ if (!visit_song)
+ return true;
+
+ LightSong song;
+ song.directory = nullptr;
+ song.uri = path;
+ song.real_uri = meta.url.c_str();
+ song.tag = &meta.tag;
+ song.mtime = 0;
+ song.start_time = song.end_time = SongTime::zero();
+
+ return !selection.Match(song) || visit_song(song, error);
+}
+
+/**
+ * Build synthetic path based on object id for search results. The use
+ * of "rootid" is arbitrary, any name that is not likely to be a top
+ * directory name would fit.
+ */
+static std::string
+songPath(const std::string &servername,
+ const std::string &objid)
+{
+ return servername + "/" + rootid + "/" + objid;
+}
+
+bool
+UpnpDatabase::SearchSongs(const ContentDirectoryService &server,
+ const char *objid,
+ const DatabaseSelection &selection,
+ VisitSong visit_song,
+ Error &error) const
+{
+ UPnPDirContent dirbuf;
+ if (!visit_song)
+ return true;
+ if (!SearchSongs(server, objid, selection, dirbuf, error))
+ return false;
+
+ for (auto &dirent : dirbuf.objects) {
+ if (dirent.type != UPnPDirObject::Type::ITEM ||
+ dirent.item_class != UPnPDirObject::ItemClass::MUSIC)
+ continue;
+
+ // We get song ids as the result of the UPnP search. But our
+ // client expects paths (e.g. we get 1$4$3788 from minidlna,
+ // but we need to translate to /Music/All_Music/Satisfaction).
+ // We can do this in two ways:
+ // - Rebuild a normal path using BuildPath() which is a kind of pwd
+ // - Build a bogus path based on the song id.
+ // The first method is nice because the returned paths are pretty, but
+ // it has two big problems:
+ // - The song paths are ambiguous: e.g. minidlna returns all search
+ // results as being from the "All Music" directory, which can
+ // contain several songs with the same title (but different objids)
+ // - The performance of BuildPath() is atrocious on very big
+ // directories, even causing timeouts in clients. And of
+ // course, 'All Music' is very big.
+ // So we return synthetic and ugly paths based on the object id,
+ // which we later have to detect.
+ const std::string path = songPath(server.getFriendlyName(),
+ dirent.m_id);
+ if (!visitSong(std::move(dirent), path.c_str(),
+ selection, visit_song,
+ error))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpDatabase::ReadNode(const ContentDirectoryService &server,
+ const char *objid, UPnPDirObject &dirent,
+ Error &error) const
+{
+ UPnPDirContent dirbuf;
+ if (!server.getMetadata(handle, objid, dirbuf, error))
+ return false;
+
+ if (dirbuf.objects.size() == 1) {
+ dirent = std::move(dirbuf.objects.front());
+ } else {
+ error.Format(upnp_domain, "Bad resource");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpDatabase::BuildPath(const ContentDirectoryService &server,
+ const UPnPDirObject& idirent,
+ std::string &path,
+ Error &error) const
+{
+ const char *pid = idirent.m_id.c_str();
+ path.clear();
+ UPnPDirObject dirent;
+ while (strcmp(pid, rootid) != 0) {
+ if (!ReadNode(server, pid, dirent, error))
+ return false;
+ pid = dirent.m_pid.c_str();
+
+ if (path.empty())
+ path = dirent.name;
+ else
+ path = PathTraitsUTF8::Build(dirent.name.c_str(),
+ path.c_str());
+ }
+
+ path = PathTraitsUTF8::Build(server.getFriendlyName(),
+ path.c_str());
+ return true;
+}
+
+// Take server and internal title pathname and return objid and metadata.
+bool
+UpnpDatabase::Namei(const ContentDirectoryService &server,
+ const std::list<std::string> &vpath,
+ UPnPDirObject &odirent,
+ Error &error) const
+{
+ if (vpath.empty()) {
+ // looking for root info
+ if (!ReadNode(server, rootid, odirent, error))
+ return false;
+
+ return true;
+ }
+
+ std::string objid(rootid);
+
+ // Walk the path elements, read each directory and try to find the next one
+ for (auto i = vpath.begin(), last = std::prev(vpath.end());; ++i) {
+ UPnPDirContent dirbuf;
+ if (!server.readDir(handle, objid.c_str(), dirbuf, error))
+ return false;
+
+ // Look for the name in the sub-container list
+ UPnPDirObject *child = dirbuf.FindObject(i->c_str());
+ if (child == nullptr) {
+ error.Format(db_domain, DB_NOT_FOUND,
+ "No such object");
+ return false;
+ }
+
+ if (i == last) {
+ odirent = std::move(*child);
+ return true;
+ }
+
+ if (child->type != UPnPDirObject::Type::CONTAINER) {
+ error.Format(db_domain, DB_NOT_FOUND,
+ "Not a container");
+ return false;
+ }
+
+ objid = std::move(child->m_id);
+ }
+}
+
+static bool
+VisitItem(const UPnPDirObject &object, const char *uri,
+ const DatabaseSelection &selection,
+ VisitSong visit_song, VisitPlaylist visit_playlist,
+ Error &error)
+{
+ assert(object.type == UPnPDirObject::Type::ITEM);
+
+ switch (object.item_class) {
+ case UPnPDirObject::ItemClass::MUSIC:
+ return !visit_song ||
+ visitSong(object, uri,
+ selection, visit_song, error);
+
+ case UPnPDirObject::ItemClass::PLAYLIST:
+ if (visit_playlist) {
+ /* Note: I've yet to see a
+ playlist item (playlists
+ seem to be usually handled
+ as containers, so I'll
+ decide what to do when I
+ see one... */
+ }
+
+ return true;
+
+ case UPnPDirObject::ItemClass::UNKNOWN:
+ return true;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+static bool
+VisitObject(const UPnPDirObject &object, const char *uri,
+ const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error)
+{
+ switch (object.type) {
+ case UPnPDirObject::Type::UNKNOWN:
+ assert(false);
+ gcc_unreachable();
+
+ case UPnPDirObject::Type::CONTAINER:
+ return !visit_directory ||
+ visit_directory(LightDirectory(uri, 0), error);
+
+ case UPnPDirObject::Type::ITEM:
+ return VisitItem(object, uri, selection,
+ visit_song, visit_playlist,
+ error);
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+// vpath is a parsed and writeable version of selection.uri. There is
+// really just one path parameter.
+bool
+UpnpDatabase::VisitServer(const ContentDirectoryService &server,
+ const std::list<std::string> &vpath,
+ const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ /* If the path begins with rootid, we know that this is a
+ song, not a directory (because that's how we set things
+ up). Just visit it. Note that the choice of rootid is
+ arbitrary, any value not likely to be the name of a top
+ directory would be ok. */
+ /* !Note: this *can't* be handled by Namei further down,
+ because the path is not valid for traversal. Besides, it's
+ just faster to access the target node directly */
+ if (!vpath.empty() && vpath.front() == rootid) {
+ switch (vpath.size()) {
+ case 1:
+ return true;
+
+ case 2:
+ break;
+
+ default:
+ error.Format(db_domain, DB_NOT_FOUND,
+ "Not found");
+ return false;
+ }
+
+ if (visit_song) {
+ UPnPDirObject dirent;
+ if (!ReadNode(server, vpath.back().c_str(), dirent,
+ error))
+ return false;
+
+ if (dirent.type != UPnPDirObject::Type::ITEM ||
+ dirent.item_class != UPnPDirObject::ItemClass::MUSIC) {
+ error.Format(db_domain, DB_NOT_FOUND,
+ "Not found");
+ return false;
+ }
+
+ std::string path = songPath(server.getFriendlyName(),
+ dirent.m_id);
+ if (!visitSong(std::move(dirent), path.c_str(),
+ selection,
+ visit_song, error))
+ return false;
+ }
+ return true;
+ }
+
+ // Translate the target path into an object id and the associated metadata.
+ UPnPDirObject tdirent;
+ if (!Namei(server, vpath, tdirent, error))
+ return false;
+
+ /* If recursive is set, this is a search... No use sending it
+ if the filter is empty. In this case, we implement limited
+ recursion (1-deep) here, which will handle the "add dir"
+ case. */
+ if (selection.recursive && selection.filter)
+ return SearchSongs(server, tdirent.m_id.c_str(), selection,
+ visit_song, error);
+
+ const char *const base_uri = selection.uri.empty()
+ ? server.getFriendlyName()
+ : selection.uri.c_str();
+
+ if (tdirent.type == UPnPDirObject::Type::ITEM) {
+ return VisitItem(tdirent, base_uri,
+ selection,
+ visit_song, visit_playlist,
+ error);
+ }
+
+ /* Target was a a container. Visit it. We could read slices
+ and loop here, but it's not useful as mpd will only return
+ data to the client when we're done anyway. */
+ UPnPDirContent dirbuf;
+ if (!server.readDir(handle, tdirent.m_id.c_str(), dirbuf,
+ error))
+ return false;
+
+ for (auto &dirent : dirbuf.objects) {
+ const std::string uri = PathTraitsUTF8::Build(base_uri,
+ dirent.name.c_str());
+ if (!VisitObject(dirent, uri.c_str(),
+ selection,
+ visit_directory,
+ visit_song, visit_playlist,
+ error))
+ return false;
+ }
+
+ return true;
+}
+
+// Deal with the possibly multiple servers, call VisitServer if needed.
+bool
+UpnpDatabase::Visit(const DatabaseSelection &selection,
+ VisitDirectory visit_directory,
+ VisitSong visit_song,
+ VisitPlaylist visit_playlist,
+ Error &error) const
+{
+ auto vpath = stringToTokens(selection.uri, "/", true);
+ if (vpath.empty()) {
+ std::vector<ContentDirectoryService> servers;
+ if (!discovery->getDirServices(servers, error))
+ return false;
+
+ for (const auto &server : servers) {
+ if (visit_directory) {
+ const LightDirectory d(server.getFriendlyName(), 0);
+ if (!visit_directory(d, error))
+ return false;
+ }
+
+ if (selection.recursive &&
+ !VisitServer(server, vpath, selection,
+ visit_directory, visit_song, visit_playlist,
+ error))
+ return false;
+ }
+
+ return true;
+ }
+
+ // We do have a path: the first element selects the server
+ std::string servername(std::move(vpath.front()));
+ vpath.pop_front();
+
+ ContentDirectoryService server;
+ if (!discovery->getServer(servername.c_str(), server, error))
+ return false;
+
+ return VisitServer(server, vpath, selection,
+ visit_directory, visit_song, visit_playlist, error);
+}
+
+bool
+UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
+ TagType tag, gcc_unused uint32_t group_mask,
+ VisitTag visit_tag,
+ Error &error) const
+{
+ // TODO: use group_mask
+
+ if (!visit_tag)
+ return true;
+
+ std::vector<ContentDirectoryService> servers;
+ if (!discovery->getDirServices(servers, error))
+ return false;
+
+ std::set<std::string> values;
+ for (auto& server : servers) {
+ UPnPDirContent dirbuf;
+ if (!SearchSongs(server, rootid, selection, dirbuf, error))
+ return false;
+
+ for (const auto &dirent : dirbuf.objects) {
+ if (dirent.type != UPnPDirObject::Type::ITEM ||
+ dirent.item_class != UPnPDirObject::ItemClass::MUSIC)
+ continue;
+
+ const char *value = dirent.tag.GetValue(tag);
+ if (value != nullptr) {
+#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+ values.emplace(value);
+#else
+ values.insert(value);
+#endif
+ }
+ }
+ }
+
+ for (const auto& value : values) {
+ TagBuilder builder;
+ builder.AddItem(tag, value.c_str());
+ if (!visit_tag(builder.Commit(), error))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpDatabase::GetStats(const DatabaseSelection &,
+ DatabaseStats &stats, Error &) const
+{
+ /* Note: this gets called before the daemonizing so we can't
+ reallyopen this would be a problem if we had real stats */
+ stats.Clear();
+ return true;
+}
+
+const DatabasePlugin upnp_db_plugin = {
+ "upnp",
+ 0,
+ UpnpDatabase::Create,
+};
diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.hxx b/src/db/plugins/upnp/UpnpDatabasePlugin.hxx
new file mode 100644
index 000000000..0228405cd
--- /dev/null
+++ b/src/db/plugins/upnp/UpnpDatabasePlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_DATABASE_PLUGIN_HXX
+#define MPD_UPNP_DATABASE_PLUGIN_HXX
+
+struct DatabasePlugin;
+
+extern const DatabasePlugin upnp_db_plugin;
+
+#endif
diff --git a/src/db/update/Archive.cxx b/src/db/update/Archive.cxx
new file mode 100644
index 000000000..fc8f1fcbf
--- /dev/null
+++ b/src/db/update/Archive.cxx
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Walk.hxx"
+#include "UpdateDomain.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "storage/StorageInterface.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "storage/FileInfo.hxx"
+#include "archive/ArchiveList.hxx"
+#include "archive/ArchivePlugin.hxx"
+#include "archive/ArchiveFile.hxx"
+#include "archive/ArchiveVisitor.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <string>
+
+#include <sys/stat.h>
+#include <string.h>
+
+void
+UpdateWalk::UpdateArchiveTree(Directory &directory, const char *name)
+{
+ const char *tmp = strchr(name, '/');
+ if (tmp) {
+ const std::string child_name(name, tmp);
+ //add dir is not there already
+ db_lock();
+ Directory *subdir =
+ directory.MakeChild(child_name.c_str());
+ subdir->device = DEVICE_INARCHIVE;
+ db_unlock();
+
+ //create directories first
+ UpdateArchiveTree(*subdir, tmp + 1);
+ } else {
+ if (strlen(name) == 0) {
+ LogWarning(update_domain,
+ "archive returned directory only");
+ return;
+ }
+
+ //add file
+ db_lock();
+ Song *song = directory.FindSong(name);
+ db_unlock();
+ if (song == nullptr) {
+ song = Song::LoadFile(storage, name, directory);
+ if (song != nullptr) {
+ db_lock();
+ directory.AddSong(song);
+ db_unlock();
+
+ modified = true;
+ FormatDefault(update_domain, "added %s/%s",
+ directory.GetPath(), name);
+ }
+ }
+ }
+}
+
+class UpdateArchiveVisitor final : public ArchiveVisitor {
+ UpdateWalk &walk;
+ Directory *directory;
+
+ public:
+ UpdateArchiveVisitor(UpdateWalk &_walk, Directory *_directory)
+ :walk(_walk), directory(_directory) {}
+
+ virtual void VisitArchiveEntry(const char *path_utf8) override {
+ FormatDebug(update_domain,
+ "adding archive file: %s", path_utf8);
+ walk.UpdateArchiveTree(*directory, path_utf8);
+ }
+};
+
+/**
+ * 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
+ */
+void
+UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
+ const FileInfo &info,
+ const ArchivePlugin &plugin)
+{
+ db_lock();
+ Directory *directory = parent.FindChild(name);
+ db_unlock();
+
+ if (directory != nullptr && directory->mtime == info.mtime &&
+ !walk_discard)
+ /* MPD has already scanned the archive, and it hasn't
+ changed since - don't consider updating it */
+ return;
+
+ const auto path_fs = storage.MapChildFS(parent.GetPath(), name);
+ if (path_fs.IsNull())
+ /* not a local file: skip, because the archive API
+ supports only local files */
+ return;
+
+ /* open archive */
+ Error error;
+ ArchiveFile *file = archive_file_open(&plugin, path_fs, error);
+ if (file == nullptr) {
+ LogError(error);
+ if (directory != nullptr)
+ editor.LockDeleteDirectory(directory);
+ return;
+ }
+
+ FormatDebug(update_domain, "archive %s opened", path_fs.c_str());
+
+ if (directory == nullptr) {
+ FormatDebug(update_domain,
+ "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 = info.mtime;
+
+ UpdateArchiveVisitor visitor(*this, directory);
+ file->Visit(visitor);
+ file->Close();
+}
+
+bool
+UpdateWalk::UpdateArchiveFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info)
+{
+ const ArchivePlugin *plugin = archive_plugin_from_suffix(suffix);
+ if (plugin == nullptr)
+ return false;
+
+ UpdateArchiveFile(directory, name, info, *plugin);
+ return true;
+}
diff --git a/src/db/update/Container.cxx b/src/db/update/Container.cxx
new file mode 100644
index 000000000..1c420fa99
--- /dev/null
+++ b/src/db/update/Container.cxx
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Walk.hxx"
+#include "UpdateDomain.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "storage/StorageInterface.hxx"
+#include "decoder/DecoderPlugin.hxx"
+#include "decoder/DecoderList.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "storage/FileInfo.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagBuilder.hxx"
+#include "Log.hxx"
+
+#include <sys/stat.h>
+
+Directory *
+UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
+ const FileInfo &info)
+{
+ Directory *directory = parent.FindChild(name);
+
+ // directory exists already
+ if (directory != nullptr) {
+ if (directory->IsMount())
+ return nullptr;
+
+ if (directory->mtime == info.mtime && !walk_discard) {
+ /* not modified */
+ return nullptr;
+ }
+
+ editor.DeleteDirectory(directory);
+ modified = true;
+ }
+
+ directory = parent.MakeChild(name);
+ directory->mtime = info.mtime;
+ return directory;
+}
+
+static bool
+SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix)
+{
+ return plugin.container_scan != nullptr &&
+ plugin.SupportsSuffix(suffix);
+}
+
+bool
+UpdateWalk::UpdateContainerFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info)
+{
+ const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){
+ return SupportsContainerSuffix(plugin, suffix);
+ });
+ if (_plugin == nullptr)
+ return false;
+ const DecoderPlugin &plugin = *_plugin;
+
+ db_lock();
+ Directory *contdir = MakeDirectoryIfModified(directory, name, info);
+ if (contdir == nullptr) {
+ /* not modified */
+ db_unlock();
+ return true;
+ }
+
+ contdir->device = DEVICE_CONTAINER;
+ db_unlock();
+
+ const auto pathname = storage.MapFS(contdir->GetPath());
+ if (pathname.IsNull()) {
+ /* not a local file: skip, because the container API
+ supports only local files */
+ editor.LockDeleteDirectory(contdir);
+ return false;
+ }
+
+ char *vtrack;
+ unsigned int tnum = 0;
+ TagBuilder tag_builder;
+ while ((vtrack = plugin.container_scan(pathname, ++tnum)) != nullptr) {
+ Song *song = Song::NewFile(vtrack, *contdir);
+
+ // shouldn't be necessary but it's there..
+ song->mtime = info.mtime;
+
+ const auto child_path_fs = AllocatedPath::Build(pathname,
+ vtrack);
+ plugin.ScanFile(child_path_fs,
+ add_tag_handler, &tag_builder);
+
+ tag_builder.Commit(song->tag);
+
+ db_lock();
+ contdir->AddSong(song);
+ db_unlock();
+
+ modified = true;
+
+ FormatDefault(update_domain, "added %s/%s",
+ directory.GetPath(), vtrack);
+ delete[] vtrack;
+ }
+
+ if (tnum == 1) {
+ editor.LockDeleteDirectory(contdir);
+ return false;
+ } else
+ return true;
+}
diff --git a/src/db/update/Editor.cxx b/src/db/update/Editor.cxx
new file mode 100644
index 000000000..4136ccdad
--- /dev/null
+++ b/src/db/update/Editor.cxx
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Editor.hxx"
+#include "Remove.hxx"
+#include "db/PlaylistVector.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "db/plugins/simple/Song.hxx"
+
+#include <assert.h>
+#include <stddef.h>
+
+void
+DatabaseEditor::DeleteSong(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) */
+ remove.Remove(del);
+
+ /* finally, all possible references gone, free it */
+ del->Free();
+
+ db_lock();
+}
+
+void
+DatabaseEditor::LockDeleteSong(Directory &parent, Song *song)
+{
+ db_lock();
+ DeleteSong(parent, song);
+ db_unlock();
+}
+
+/**
+ * Recursively remove all sub directories and songs from a directory,
+ * leaving an empty directory.
+ *
+ * Caller must lock the #db_mutex.
+ */
+inline void
+DatabaseEditor::ClearDirectory(Directory &directory)
+{
+ directory.ForEachChildSafe([this](Directory &child){
+ DeleteDirectory(&child);
+ });
+
+ directory.ForEachSongSafe([this, &directory](Song &song){
+ assert(song.parent == &directory);
+ DeleteSong(directory, &song);
+ });
+}
+
+void
+DatabaseEditor::DeleteDirectory(Directory *directory)
+{
+ assert(directory->parent != nullptr);
+
+ ClearDirectory(*directory);
+
+ directory->Delete();
+}
+
+void
+DatabaseEditor::LockDeleteDirectory(Directory *directory)
+{
+ db_lock();
+ DeleteDirectory(directory);
+ db_unlock();
+}
+
+bool
+DatabaseEditor::DeleteNameIn(Directory &parent, const char *name)
+{
+ bool modified = false;
+
+ db_lock();
+ Directory *directory = parent.FindChild(name);
+
+ if (directory != nullptr) {
+ DeleteDirectory(directory);
+ modified = true;
+ }
+
+ Song *song = parent.FindSong(name);
+ if (song != nullptr) {
+ DeleteSong(parent, song);
+ modified = true;
+ }
+
+ parent.playlists.erase(name);
+
+ db_unlock();
+
+ return modified;
+}
diff --git a/src/db/update/Editor.hxx b/src/db/update/Editor.hxx
new file mode 100644
index 000000000..58e23ed7a
--- /dev/null
+++ b/src/db/update/Editor.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_DATABASE_HXX
+#define MPD_UPDATE_DATABASE_HXX
+
+#include "check.h"
+#include "Remove.hxx"
+#include "Compiler.h"
+
+struct Directory;
+struct Song;
+class UpdateRemoveService;
+
+class DatabaseEditor final {
+ UpdateRemoveService remove;
+
+public:
+ DatabaseEditor(EventLoop &_loop, DatabaseListener &_listener)
+ :remove(_loop, _listener) {}
+
+ /**
+ * Caller must lock the #db_mutex.
+ */
+ void DeleteSong(Directory &parent, Song *song);
+
+ /**
+ * DeleteSong() with automatic locking.
+ */
+ void LockDeleteSong(Directory &parent, Song *song);
+
+ /**
+ * Recursively free a directory and all its contents.
+ *
+ * Caller must lock the #db_mutex.
+ */
+ void DeleteDirectory(Directory *directory);
+
+ /**
+ * DeleteDirectory() with automatic locking.
+ */
+ void LockDeleteDirectory(Directory *directory);
+
+ /**
+ * Caller must NOT lock the #db_mutex.
+ *
+ * @return true if the database was modified
+ */
+ bool DeleteNameIn(Directory &parent, const char *name);
+
+private:
+ void ClearDirectory(Directory &directory);
+};
+
+#endif
diff --git a/src/db/update/ExcludeList.cxx b/src/db/update/ExcludeList.cxx
new file mode 100644
index 000000000..cf92ac8f7
--- /dev/null
+++ b/src/db/update/ExcludeList.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#include "config.h"
+#include "ExcludeList.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+static constexpr Domain exclude_list_domain("exclude_list");
+
+bool
+ExcludeList::LoadFile(Path path_fs)
+{
+#ifdef HAVE_GLIB
+ FILE *file = FOpen(path_fs, FOpenMode::ReadText);
+ if (file == nullptr) {
+ const int e = errno;
+ if (e != ENOENT) {
+ const auto path_utf8 = path_fs.ToUTF8();
+ FormatErrno(exclude_list_domain,
+ "Failed to open %s",
+ path_utf8.c_str());
+ }
+
+ return false;
+ }
+
+ char line[1024];
+ while (fgets(line, sizeof(line), file) != nullptr) {
+ char *p = strchr(line, '#');
+ if (p != nullptr)
+ *p = 0;
+
+ p = Strip(line);
+ if (*p != 0)
+ patterns.emplace_front(p);
+ }
+
+ fclose(file);
+#else
+ // TODO: implement
+ (void)path_fs;
+#endif
+
+ return true;
+}
+
+bool
+ExcludeList::Check(Path name_fs) const
+{
+ assert(!name_fs.IsNull());
+
+ /* XXX include full path name in check */
+
+#ifdef HAVE_GLIB
+ for (const auto &i : patterns)
+ if (i.Check(name_fs.c_str()))
+ return true;
+#else
+ // TODO: implement
+ (void)name_fs;
+#endif
+
+ return false;
+}
diff --git a/src/db/update/ExcludeList.hxx b/src/db/update/ExcludeList.hxx
new file mode 100644
index 000000000..ef6c4d53e
--- /dev/null
+++ b/src/db/update/ExcludeList.hxx
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#ifndef MPD_EXCLUDE_H
+#define MPD_EXCLUDE_H
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <forward_list>
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
+class Path;
+
+class ExcludeList {
+#ifdef HAVE_GLIB
+ 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;
+#else
+ // TODO: implement
+#endif
+
+public:
+ gcc_pure
+ bool IsEmpty() const {
+#ifdef HAVE_GLIB
+ return patterns.empty();
+#else
+ // TODO: implement
+ return true;
+#endif
+ }
+
+ /**
+ * Loads and parses a .mpdignore file.
+ */
+ bool LoadFile(Path path_fs);
+
+ /**
+ * Checks whether one of the patterns in the .mpdignore file matches
+ * the specified file name.
+ */
+ bool Check(Path name_fs) const;
+};
+
+
+#endif
diff --git a/src/db/update/InotifyDomain.cxx b/src/db/update/InotifyDomain.cxx
new file mode 100644
index 000000000..4a3ab2d79
--- /dev/null
+++ b/src/db/update/InotifyDomain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "InotifyDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain inotify_domain("inotify");
diff --git a/src/db/update/InotifyDomain.hxx b/src/db/update/InotifyDomain.hxx
new file mode 100644
index 000000000..ad6202361
--- /dev/null
+++ b/src/db/update/InotifyDomain.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INOTIFY_DOMAIN_HXX
+#define MPD_INOTIFY_DOMAIN_HXX
+
+extern const class Domain inotify_domain;
+
+#endif
diff --git a/src/db/update/InotifyQueue.cxx b/src/db/update/InotifyQueue.cxx
new file mode 100644
index 000000000..013200f98
--- /dev/null
+++ b/src/db/update/InotifyQueue.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "InotifyQueue.hxx"
+#include "InotifyDomain.hxx"
+#include "Service.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+/**
+ * Wait this long after the last change before calling
+ * update_enqueue(). This increases the probability that updates can
+ * be bundled.
+ */
+static constexpr unsigned 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;
+ }
+
+ FormatDebug(inotify_domain, "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/db/update/InotifyQueue.hxx b/src/db/update/InotifyQueue.hxx
new file mode 100644
index 000000000..a9abc2969
--- /dev/null
+++ b/src/db/update/InotifyQueue.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INOTIFY_QUEUE_HXX
+#define MPD_INOTIFY_QUEUE_HXX
+
+#include "event/TimeoutMonitor.hxx"
+#include "Compiler.h"
+
+#include <list>
+#include <string>
+
+class UpdateService;
+
+class InotifyQueue final : private TimeoutMonitor {
+ UpdateService &update;
+
+ std::list<std::string> queue;
+
+public:
+ InotifyQueue(EventLoop &_loop, UpdateService &_update)
+ :TimeoutMonitor(_loop), update(_update) {}
+
+ void Enqueue(const char *uri_utf8);
+
+private:
+ virtual void OnTimeout() override;
+};
+
+#endif
diff --git a/src/db/update/InotifySource.cxx b/src/db/update/InotifySource.cxx
new file mode 100644
index 000000000..233504ad6
--- /dev/null
+++ b/src/db/update/InotifySource.cxx
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "InotifySource.hxx"
+#include "InotifyDomain.hxx"
+#include "util/Error.hxx"
+#include "system/fd_util.h"
+#include "system/FatalError.hxx"
+#include "Log.hxx"
+
+#include <algorithm>
+
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+
+bool
+InotifySource::OnSocketReady(gcc_unused unsigned flags)
+{
+ uint8_t buffer[4096];
+ static_assert(sizeof(buffer) >= sizeof(struct inotify_event) + NAME_MAX + 1,
+ "inotify buffer too small");
+
+ ssize_t nbytes = read(Get(), buffer, sizeof(buffer));
+ if (nbytes < 0)
+ FatalSystemError("Failed to read from inotify");
+ if (nbytes == 0)
+ FatalError("end of file from inotify");
+
+ const uint8_t *p = buffer, *const end = p + nbytes;
+
+ while (true) {
+ const size_t remaining = end - p;
+ const struct inotify_event *event =
+ (const struct inotify_event *)p;
+ if (remaining < sizeof(*event) ||
+ remaining < sizeof(*event) + event->len)
+ break;
+
+ const char *name;
+ if (event->len > 0 && event->name[event->len - 1] == 0)
+ name = event->name;
+ else
+ name = nullptr;
+
+ callback(event->wd, event->mask, name, callback_ctx);
+ p += 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)
+{
+ ScheduleRead();
+
+}
+
+InotifySource *
+InotifySource::Create(EventLoop &loop,
+ mpd_inotify_callback_t callback, void *callback_ctx,
+ Error &error)
+{
+ int fd = inotify_init_cloexec();
+ if (fd < 0) {
+ error.SetErrno("inotify_init() has failed");
+ return nullptr;
+ }
+
+ return new InotifySource(loop, callback, callback_ctx, fd);
+}
+
+int
+InotifySource::Add(const char *path_fs, unsigned mask, Error &error)
+{
+ int wd = inotify_add_watch(Get(), path_fs, mask);
+ if (wd < 0)
+ error.SetErrno("inotify_add_watch() has failed");
+
+ return wd;
+}
+
+void
+InotifySource::Remove(unsigned wd)
+{
+ int ret = inotify_rm_watch(Get(), wd);
+ if (ret < 0 && errno != EINVAL)
+ LogErrno(inotify_domain, "inotify_rm_watch() has failed");
+
+ /* EINVAL may happen here when the file has been deleted; the
+ kernel seems to auto-unregister deleted files */
+}
diff --git a/src/db/update/InotifySource.hxx b/src/db/update/InotifySource.hxx
new file mode 100644
index 000000000..2557680a0
--- /dev/null
+++ b/src/db/update/InotifySource.hxx
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INOTIFY_SOURCE_HXX
+#define MPD_INOTIFY_SOURCE_HXX
+
+#include "event/SocketMonitor.hxx"
+#include "Compiler.h"
+
+class Error;
+
+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;
+
+ InotifySource(EventLoop &_loop,
+ mpd_inotify_callback_t callback, void *ctx, int fd);
+
+public:
+ ~InotifySource() {
+ Close();
+ }
+
+ /**
+ * Creates a new inotify source and registers it in the GLib main
+ * loop.
+ *
+ * @param a callback invoked for events received from the kernel
+ */
+ static InotifySource *Create(EventLoop &_loop,
+ mpd_inotify_callback_t callback,
+ void *ctx,
+ Error &error);
+
+ /**
+ * Adds a path to the notify list.
+ *
+ * @return a watch descriptor or -1 on error
+ */
+ int Add(const char *path_fs, unsigned mask, Error &error);
+
+ /**
+ * 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/db/update/InotifyUpdate.cxx b/src/db/update/InotifyUpdate.cxx
new file mode 100644
index 000000000..26bdddf8d
--- /dev/null
+++ b/src/db/update/InotifyUpdate.cxx
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "InotifyUpdate.hxx"
+#include "InotifySource.hxx"
+#include "InotifyQueue.hxx"
+#include "InotifyDomain.hxx"
+#include "storage/StorageInterface.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <string>
+#include <map>
+#include <forward_list>
+
+#include <assert.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <dirent.h>
+
+static constexpr unsigned IN_MASK =
+#ifdef IN_ONLYDIR
+ IN_ONLYDIR|
+#endif
+ IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
+ |IN_MOVE|IN_MOVE_SELF;
+
+struct WatchDirectory {
+ WatchDirectory *parent;
+
+ AllocatedPath name;
+
+ int descriptor;
+
+ std::forward_list<WatchDirectory> children;
+
+ template<typename N>
+ WatchDirectory(WatchDirectory *_parent, N &&_name,
+ int _descriptor)
+ :parent(_parent), name(std::forward<N>(_name)),
+ descriptor(_descriptor) {}
+
+ WatchDirectory(const WatchDirectory &) = delete;
+ WatchDirectory &operator=(const WatchDirectory &) = delete;
+
+ gcc_pure
+ unsigned GetDepth() const;
+
+ gcc_pure
+ AllocatedPath GetUriFS() const;
+};
+
+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 != nullptr);
+
+ if (directory->parent == nullptr) {
+ LogWarning(inotify_domain,
+ "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;
+ });
+}
+
+AllocatedPath
+WatchDirectory::GetUriFS() const
+{
+ if (parent == nullptr)
+ return AllocatedPath::Null();
+
+ const auto uri = parent->GetUriFS();
+ if (uri.IsNull())
+ return name;
+
+ return AllocatedPath::Build(uri, name);
+}
+
+/* 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') != nullptr;
+}
+
+static void
+recursive_watch_subdirectories(WatchDirectory *directory,
+ const AllocatedPath &path_fs, unsigned depth)
+{
+ Error error;
+ DIR *dir;
+ struct dirent *ent;
+
+ assert(directory != nullptr);
+ assert(depth <= inotify_max_depth);
+ assert(!path_fs.IsNull());
+
+ ++depth;
+
+ if (depth > inotify_max_depth)
+ return;
+
+ dir = opendir(path_fs.c_str());
+ if (dir == nullptr) {
+ FormatErrno(inotify_domain,
+ "Failed to open directory %s", path_fs.c_str());
+ return;
+ }
+
+ while ((ent = readdir(dir))) {
+ struct stat st;
+ int ret;
+
+ if (skip_path(ent->d_name))
+ continue;
+
+ const auto child_path_fs =
+ AllocatedPath::Build(path_fs, ent->d_name);
+ ret = StatFile(child_path_fs, st);
+ if (ret < 0) {
+ FormatErrno(inotify_domain,
+ "Failed to stat %s",
+ child_path_fs.c_str());
+ continue;
+ }
+
+ if (!S_ISDIR(st.st_mode))
+ continue;
+
+ ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK,
+ error);
+ if (ret < 0) {
+ FormatError(error,
+ "Failed to register %s",
+ child_path_fs.c_str());
+ error.Clear();
+ continue;
+ }
+
+ WatchDirectory *child = tree_find_watch_directory(ret);
+ if (child != nullptr)
+ /* already being watched */
+ continue;
+
+ directory->children.emplace_front(directory,
+ AllocatedPath::FromFS(ent->d_name),
+ ret);
+ child = &directory->children.front();
+
+ tree_add_watch_directory(child);
+
+ recursive_watch_subdirectories(child, child_path_fs, depth);
+ }
+
+ closedir(dir);
+}
+
+gcc_pure
+unsigned
+WatchDirectory::GetDepth() const
+{
+ const WatchDirectory *d = this;
+ unsigned depth = 0;
+ while ((d = d->parent) != nullptr)
+ ++depth;
+
+ return depth;
+}
+
+static void
+mpd_inotify_callback(int wd, unsigned mask,
+ gcc_unused const char *name, gcc_unused void *ctx)
+{
+ WatchDirectory *directory;
+
+ /*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
+
+ directory = tree_find_watch_directory(wd);
+ if (directory == nullptr)
+ return;
+
+ const auto uri_fs = directory->GetUriFS();
+
+ if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
+ 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 auto &root = inotify_root->name;
+
+ const auto path_fs = uri_fs.IsNull()
+ ? root
+ : AllocatedPath::Build(root, uri_fs.c_str());
+
+ recursive_watch_subdirectories(directory, path_fs,
+ directory->GetDepth());
+ }
+
+ if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 ||
+ /* at the maximum depth, we watch out for newly created
+ directories */
+ (directory->GetDepth() == 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.IsNull()) {
+ const std::string uri_utf8 = uri_fs.ToUTF8();
+ if (!uri_utf8.empty())
+ inotify_queue->Enqueue(uri_utf8.c_str());
+ }
+ else
+ inotify_queue->Enqueue("");
+ }
+}
+
+void
+mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
+ unsigned max_depth)
+{
+ LogDebug(inotify_domain, "initializing inotify");
+
+ const auto path = storage.MapFS("");
+ if (path.IsNull()) {
+ LogDebug(inotify_domain, "no music directory configured");
+ return;
+ }
+
+ Error error;
+ inotify_source = InotifySource::Create(loop,
+ mpd_inotify_callback, nullptr,
+ error);
+ if (inotify_source == nullptr) {
+ LogError(error);
+ return;
+ }
+
+ inotify_max_depth = max_depth;
+
+ int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error);
+ if (descriptor < 0) {
+ LogError(error);
+ delete inotify_source;
+ inotify_source = nullptr;
+ return;
+ }
+
+ inotify_root = new WatchDirectory(nullptr, path, descriptor);
+
+ tree_add_watch_directory(inotify_root);
+
+ recursive_watch_subdirectories(inotify_root, path, 0);
+
+ inotify_queue = new InotifyQueue(loop, update);
+
+ LogDebug(inotify_domain, "watching music directory");
+}
+
+void
+mpd_inotify_finish(void)
+{
+ if (inotify_source == nullptr)
+ return;
+
+ delete inotify_queue;
+ delete inotify_source;
+ delete inotify_root;
+ inotify_directories.clear();
+}
diff --git a/src/db/update/InotifyUpdate.hxx b/src/db/update/InotifyUpdate.hxx
new file mode 100644
index 000000000..0f78db71f
--- /dev/null
+++ b/src/db/update/InotifyUpdate.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INOTIFY_UPDATE_HXX
+#define MPD_INOTIFY_UPDATE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+class EventLoop;
+class Storage;
+class UpdateService;
+
+void
+mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
+ unsigned max_depth);
+
+void
+mpd_inotify_finish(void);
+
+#endif
diff --git a/src/db/update/Queue.cxx b/src/db/update/Queue.cxx
new file mode 100644
index 000000000..6d6d80131
--- /dev/null
+++ b/src/db/update/Queue.cxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Queue.hxx"
+
+bool
+UpdateQueue::Push(SimpleDatabase &db, Storage &storage,
+ const char *path, bool discard, unsigned id)
+{
+ if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE)
+ return false;
+
+ update_queue.emplace_back(db, storage, path, discard, id);
+ return true;
+}
+
+UpdateQueueItem
+UpdateQueue::Pop()
+{
+ if (update_queue.empty())
+ return UpdateQueueItem();
+
+ auto i = std::move(update_queue.front());
+ update_queue.pop_front();
+ return i;
+}
+
+void
+UpdateQueue::Erase(SimpleDatabase &db)
+{
+ for (auto i = update_queue.begin(), end = update_queue.end();
+ i != end;) {
+ if (i->db == &db)
+ i = update_queue.erase(i);
+ else
+ ++i;
+ }
+}
+
+void
+UpdateQueue::Erase(Storage &storage)
+{
+ for (auto i = update_queue.begin(), end = update_queue.end();
+ i != end;) {
+ if (i->storage == &storage)
+ i = update_queue.erase(i);
+ else
+ ++i;
+ }
+}
diff --git a/src/db/update/Queue.hxx b/src/db/update/Queue.hxx
new file mode 100644
index 000000000..9064ea481
--- /dev/null
+++ b/src/db/update/Queue.hxx
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_QUEUE_HXX
+#define MPD_UPDATE_QUEUE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <string>
+#include <list>
+
+class SimpleDatabase;
+class Storage;
+
+struct UpdateQueueItem {
+ SimpleDatabase *db;
+ Storage *storage;
+
+ std::string path_utf8;
+ unsigned id;
+ bool discard;
+
+ UpdateQueueItem():id(0) {}
+
+ UpdateQueueItem(SimpleDatabase &_db,
+ Storage &_storage,
+ const char *_path, bool _discard,
+ unsigned _id)
+ :db(&_db), storage(&_storage), path_utf8(_path),
+ id(_id), discard(_discard) {}
+
+ bool IsDefined() const {
+ return id != 0;
+ }
+};
+
+class UpdateQueue {
+ static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32;
+
+ std::list<UpdateQueueItem> update_queue;
+
+public:
+ gcc_nonnull_all
+ bool Push(SimpleDatabase &db, Storage &storage,
+ const char *path, bool discard, unsigned id);
+
+ UpdateQueueItem Pop();
+
+ void Clear() {
+ update_queue.clear();
+ }
+
+ gcc_nonnull_all
+ void Erase(SimpleDatabase &db);
+
+ gcc_nonnull_all
+ void Erase(Storage &storage);
+};
+
+#endif
diff --git a/src/db/update/Remove.cxx b/src/db/update/Remove.cxx
new file mode 100644
index 000000000..dfada05b2
--- /dev/null
+++ b/src/db/update/Remove.cxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Remove.hxx"
+#include "UpdateDomain.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "db/LightSong.hxx"
+#include "db/DatabaseListener.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+/**
+ * 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.
+ */
+void
+UpdateRemoveService::RunDeferred()
+{
+ assert(removed_song != nullptr);
+
+ {
+ const auto uri = removed_song->GetURI();
+ FormatDefault(update_domain, "removing %s", uri.c_str());
+ }
+
+ listener.OnDatabaseSongRemoved(removed_song->Export());
+
+ /* clear "removed_song" and send signal to update thread */
+ remove_mutex.lock();
+ removed_song = nullptr;
+ remove_cond.signal();
+ remove_mutex.unlock();
+}
+
+void
+UpdateRemoveService::Remove(const Song *song)
+{
+ assert(removed_song == nullptr);
+
+ removed_song = song;
+
+ DeferredMonitor::Schedule();
+
+ remove_mutex.lock();
+
+ while (removed_song != nullptr)
+ remove_cond.wait(remove_mutex);
+
+ remove_mutex.unlock();
+}
diff --git a/src/db/update/Remove.hxx b/src/db/update/Remove.hxx
new file mode 100644
index 000000000..ce6d77d47
--- /dev/null
+++ b/src/db/update/Remove.hxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_REMOVE_HXX
+#define MPD_UPDATE_REMOVE_HXX
+
+#include "check.h"
+#include "event/DeferredMonitor.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "Compiler.h"
+
+struct Song;
+class DatabaseListener;
+
+/**
+ * This class handles #Song removal. It defers the action to the main
+ * thread to ensure that all references to the #Song are gone.
+ */
+class UpdateRemoveService final : DeferredMonitor {
+ DatabaseListener &listener;
+
+ Mutex remove_mutex;
+ Cond remove_cond;
+
+ const Song *removed_song;
+
+public:
+ UpdateRemoveService(EventLoop &_loop, DatabaseListener &_listener)
+ :DeferredMonitor(_loop), listener(_listener),
+ removed_song(nullptr){}
+
+ /**
+ * 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 Remove(const Song *song);
+
+private:
+ /* virtual methods from class DeferredMonitor */
+ virtual void RunDeferred() override;
+};
+
+#endif
diff --git a/src/db/update/Service.cxx b/src/db/update/Service.cxx
new file mode 100644
index 000000000..e8a1f6b02
--- /dev/null
+++ b/src/db/update/Service.cxx
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Service.hxx"
+#include "Walk.hxx"
+#include "UpdateDomain.hxx"
+#include "db/DatabaseListener.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "storage/CompositeStorage.hxx"
+#include "Idle.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+#include "Instance.hxx"
+#include "system/FatalError.hxx"
+#include "thread/Id.hxx"
+#include "thread/Thread.hxx"
+#include "thread/Util.hxx"
+
+#ifndef NDEBUG
+#include "event/Loop.hxx"
+#endif
+
+#include <assert.h>
+
+UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
+ CompositeStorage &_storage,
+ DatabaseListener &_listener)
+ :DeferredMonitor(_loop),
+ db(_db), storage(_storage),
+ listener(_listener),
+ progress(UPDATE_PROGRESS_IDLE),
+ update_task_id(0),
+ walk(nullptr)
+{
+}
+
+UpdateService::~UpdateService()
+{
+ CancelAllAsync();
+
+ if (update_thread.IsDefined())
+ update_thread.Join();
+
+ delete walk;
+}
+
+void
+UpdateService::CancelAllAsync()
+{
+ assert(GetEventLoop().IsInsideOrNull());
+
+ queue.Clear();
+
+ if (walk != nullptr)
+ walk->Cancel();
+}
+
+void
+UpdateService::CancelMount(const char *uri)
+{
+ /* determine which (mounted) database will be updated and what
+ storage will be scanned */
+
+ db_lock();
+ const auto lr = db.GetRoot().LookupDirectory(uri);
+ db_unlock();
+
+ if (!lr.directory->IsMount())
+ return;
+
+ bool cancel_current = false;
+
+ Storage *storage2 = storage.GetMount(uri);
+ if (storage2 != nullptr) {
+ queue.Erase(*storage2);
+ cancel_current = next.IsDefined() && next.storage == storage2;
+ }
+
+ Database &_db2 = *lr.directory->mounted_database;
+ if (_db2.IsPlugin(simple_db_plugin)) {
+ SimpleDatabase &db2 = static_cast<SimpleDatabase &>(_db2);
+ queue.Erase(db2);
+ cancel_current |= next.IsDefined() && next.db == &db2;
+ }
+
+ if (cancel_current && walk != nullptr) {
+ walk->Cancel();
+
+ if (update_thread.IsDefined())
+ update_thread.Join();
+ }
+}
+
+inline void
+UpdateService::Task()
+{
+ assert(walk != nullptr);
+
+ if (!next.path_utf8.empty())
+ FormatDebug(update_domain, "starting: %s",
+ next.path_utf8.c_str());
+ else
+ LogDebug(update_domain, "starting");
+
+ SetThreadIdlePriority();
+
+ modified = walk->Walk(next.db->GetRoot(), next.path_utf8.c_str(),
+ next.discard);
+
+ if (modified || !next.db->FileExists()) {
+ Error error;
+ if (!next.db->Save(error))
+ LogError(error, "Failed to save database");
+ }
+
+ if (!next.path_utf8.empty())
+ FormatDebug(update_domain, "finished: %s",
+ next.path_utf8.c_str());
+ else
+ LogDebug(update_domain, "finished");
+
+ progress = UPDATE_PROGRESS_DONE;
+ DeferredMonitor::Schedule();
+}
+
+void
+UpdateService::Task(void *ctx)
+{
+ UpdateService &service = *(UpdateService *)ctx;
+ return service.Task();
+}
+
+void
+UpdateService::StartThread(UpdateQueueItem &&i)
+{
+ assert(GetEventLoop().IsInsideOrNull());
+ assert(walk == nullptr);
+
+ progress = UPDATE_PROGRESS_RUNNING;
+ modified = false;
+
+ next = std::move(i);
+ walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
+
+ Error error;
+ if (!update_thread.Start(Task, this, error))
+ FatalError(error);
+
+ FormatDebug(update_domain,
+ "spawned thread for update job id %i", next.id);
+}
+
+unsigned
+UpdateService::GenerateId()
+{
+ unsigned id = update_task_id + 1;
+ if (id > update_task_id_max)
+ id = 1;
+ return id;
+}
+
+unsigned
+UpdateService::Enqueue(const char *path, bool discard)
+{
+ assert(GetEventLoop().IsInsideOrNull());
+
+ /* determine which (mounted) database will be updated and what
+ storage will be scanned */
+ SimpleDatabase *db2;
+ Storage *storage2;
+
+ db_lock();
+ const auto lr = db.GetRoot().LookupDirectory(path);
+ db_unlock();
+ if (lr.directory->IsMount()) {
+ /* follow the mountpoint, update the mounted
+ database */
+
+ Database &_db2 = *lr.directory->mounted_database;
+ if (!_db2.IsPlugin(simple_db_plugin))
+ /* cannot update this type of database */
+ return 0;
+
+ db2 = static_cast<SimpleDatabase *>(&_db2);
+
+ if (lr.uri == nullptr) {
+ storage2 = storage.GetMount(path);
+ path = "";
+ } else {
+ assert(lr.uri > path);
+ assert(lr.uri < path + strlen(path));
+ assert(lr.uri[-1] == '/');
+
+ const std::string mountpoint(path, lr.uri - 1);
+ storage2 = storage.GetMount(mountpoint.c_str());
+ path = lr.uri;
+ }
+ } else {
+ /* use the "root" database/storage */
+
+ db2 = &db;
+ storage2 = storage.GetMount("");
+ }
+
+ if (storage2 == nullptr)
+ /* no storage found at this mount point - should not
+ happen */
+ return 0;
+
+ if (progress != UPDATE_PROGRESS_IDLE) {
+ const unsigned id = GenerateId();
+ if (!queue.Push(*db2, *storage2, path, discard, id))
+ return 0;
+
+ update_task_id = id;
+ return id;
+ }
+
+ const unsigned id = update_task_id = GenerateId();
+ StartThread(UpdateQueueItem(*db2, *storage2, path, discard, id));
+
+ idle_add(IDLE_UPDATE);
+
+ return id;
+}
+
+/**
+ * Called in the main thread after the database update is finished.
+ */
+void
+UpdateService::RunDeferred()
+{
+ assert(progress == UPDATE_PROGRESS_DONE);
+ assert(next.IsDefined());
+ assert(walk != nullptr);
+
+ /* wait for thread to finish only if it wasn't cancelled by
+ CancelMount() */
+ if (update_thread.IsDefined())
+ update_thread.Join();
+
+ delete walk;
+ walk = nullptr;
+
+ next = UpdateQueueItem();
+
+ idle_add(IDLE_UPDATE);
+
+ if (modified)
+ /* send "idle" events */
+ listener.OnDatabaseModified();
+
+ auto i = queue.Pop();
+ if (i.IsDefined()) {
+ /* schedule the next path */
+ StartThread(std::move(i));
+ } else {
+ progress = UPDATE_PROGRESS_IDLE;
+ }
+}
diff --git a/src/db/update/Service.hxx b/src/db/update/Service.hxx
new file mode 100644
index 000000000..feaeaebc5
--- /dev/null
+++ b/src/db/update/Service.hxx
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_SERVICE_HXX
+#define MPD_UPDATE_SERVICE_HXX
+
+#include "check.h"
+#include "Queue.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "thread/Thread.hxx"
+#include "Compiler.h"
+
+class SimpleDatabase;
+class DatabaseListener;
+class UpdateWalk;
+class CompositeStorage;
+
+/**
+ * This class manages the update queue and runs the update thread.
+ */
+class UpdateService final : DeferredMonitor {
+ enum Progress {
+ UPDATE_PROGRESS_IDLE = 0,
+ UPDATE_PROGRESS_RUNNING = 1,
+ UPDATE_PROGRESS_DONE = 2
+ };
+
+ SimpleDatabase &db;
+ CompositeStorage &storage;
+
+ DatabaseListener &listener;
+
+ Progress progress;
+
+ bool modified;
+
+ Thread update_thread;
+
+ static const unsigned update_task_id_max = 1 << 15;
+
+ unsigned update_task_id;
+
+ UpdateQueue queue;
+
+ UpdateQueueItem next;
+
+ UpdateWalk *walk;
+
+public:
+ UpdateService(EventLoop &_loop, SimpleDatabase &_db,
+ CompositeStorage &_storage,
+ DatabaseListener &_listener);
+
+ ~UpdateService();
+
+ /**
+ * Returns a non-zero job id when we are currently updating
+ * the database.
+ */
+ unsigned GetId() const {
+ return next.id;
+ }
+
+ /**
+ * Add this path to the database update queue.
+ *
+ * @param path a path to update; if an empty string,
+ * the whole music directory is updated
+ * @return the job id, or 0 on error
+ */
+ gcc_nonnull_all
+ unsigned Enqueue(const char *path, bool discard);
+
+ /**
+ * Clear the queue and cancel the current update. Does not
+ * wait for the thread to exit.
+ */
+ void CancelAllAsync();
+
+ /**
+ * Cancel all updates for the given mount point. If an update
+ * is already running for it, the method will wait for
+ * cancellation to complete.
+ */
+ void CancelMount(const char *uri);
+
+private:
+ /* virtual methods from class DeferredMonitor */
+ virtual void RunDeferred() override;
+
+ /* the update thread */
+ void Task();
+ static void Task(void *ctx);
+
+ void StartThread(UpdateQueueItem &&i);
+
+ unsigned GenerateId();
+};
+
+#endif
diff --git a/src/db/update/UpdateDomain.cxx b/src/db/update/UpdateDomain.cxx
new file mode 100644
index 000000000..80ad4fd22
--- /dev/null
+++ b/src/db/update/UpdateDomain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "UpdateDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain update_domain("update");
diff --git a/src/db/update/UpdateDomain.hxx b/src/db/update/UpdateDomain.hxx
new file mode 100644
index 000000000..a6e994390
--- /dev/null
+++ b/src/db/update/UpdateDomain.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_DOMAIN_HXX
+#define MPD_UPDATE_DOMAIN_HXX
+
+extern const class Domain update_domain;
+
+#endif
diff --git a/src/db/update/UpdateIO.cxx b/src/db/update/UpdateIO.cxx
new file mode 100644
index 000000000..9e43d1289
--- /dev/null
+++ b/src/db/update/UpdateIO.cxx
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "UpdateIO.hxx"
+#include "UpdateDomain.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "storage/FileInfo.hxx"
+#include "storage/StorageInterface.hxx"
+#include "fs/Traits.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <errno.h>
+#include <unistd.h>
+
+bool
+GetInfo(Storage &storage, const char *uri_utf8, FileInfo &info)
+{
+ Error error;
+ bool success = storage.GetInfo(uri_utf8, true, info, error);
+ if (!success)
+ LogError(error);
+ return success;
+}
+
+bool
+GetInfo(StorageDirectoryReader &reader, FileInfo &info)
+{
+ Error error;
+ bool success = reader.GetInfo(true, info, error);
+ if (!success)
+ LogError(error);
+ return success;
+}
+
+bool
+DirectoryExists(Storage &storage, const Directory &directory)
+{
+ FileInfo info;
+ if (!storage.GetInfo(directory.GetPath(), true, info, IgnoreError()))
+ return false;
+
+ return directory.device == DEVICE_INARCHIVE ||
+ directory.device == DEVICE_CONTAINER
+ ? info.IsRegular()
+ : info.IsDirectory();
+}
+
+static bool
+GetDirectoryChildInfo(Storage &storage, const Directory &directory,
+ const char *name_utf8, FileInfo &info, Error &error)
+{
+ const auto uri_utf8 = PathTraitsUTF8::Build(directory.GetPath(),
+ name_utf8);
+ return storage.GetInfo(uri_utf8.c_str(), true, info, error);
+}
+
+bool
+directory_child_is_regular(Storage &storage, const Directory &directory,
+ const char *name_utf8)
+{
+ FileInfo info;
+ return GetDirectoryChildInfo(storage, directory, name_utf8, info,
+ IgnoreError()) &&
+ info.IsRegular();
+}
+
+bool
+directory_child_access(Storage &storage, const Directory &directory,
+ const char *name, int mode)
+{
+#ifdef WIN32
+ /* CheckAccess() is useless on WIN32 */
+ (void)storage;
+ (void)directory;
+ (void)name;
+ (void)mode;
+ return true;
+#else
+ const auto path = storage.MapChildFS(directory.GetPath(), name);
+ if (path.IsNull())
+ /* does not point to local file: silently ignore the
+ check */
+ return true;
+
+ return CheckAccess(path, mode) || errno != EACCES;
+#endif
+}
diff --git a/src/db/update/UpdateIO.hxx b/src/db/update/UpdateIO.hxx
new file mode 100644
index 000000000..2dbb4ae83
--- /dev/null
+++ b/src/db/update/UpdateIO.hxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_IO_HXX
+#define MPD_UPDATE_IO_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+struct Directory;
+struct FileInfo;
+class Storage;
+class StorageDirectoryReader;
+
+/**
+ * Wrapper for Storage::GetInfo() that logs errors instead of
+ * returning them.
+ */
+bool
+GetInfo(Storage &storage, const char *uri_utf8, FileInfo &info);
+
+/**
+ * Wrapper for LocalDirectoryReader::GetInfo() that logs errors
+ * instead of returning them.
+ */
+bool
+GetInfo(StorageDirectoryReader &reader, FileInfo &info);
+
+gcc_pure
+bool
+DirectoryExists(Storage &storage, const Directory &directory);
+
+gcc_pure
+bool
+directory_child_is_regular(Storage &storage, const Directory &directory,
+ const char *name_utf8);
+
+/**
+ * Checks if the given permissions on the mapped file are given.
+ */
+gcc_pure
+bool
+directory_child_access(Storage &storage, const Directory &directory,
+ const char *name, int mode);
+
+#endif
diff --git a/src/db/update/UpdateSong.cxx b/src/db/update/UpdateSong.cxx
new file mode 100644
index 000000000..005bf8992
--- /dev/null
+++ b/src/db/update/UpdateSong.cxx
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Walk.hxx"
+#include "UpdateIO.hxx"
+#include "UpdateDomain.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "decoder/DecoderList.hxx"
+#include "storage/FileInfo.hxx"
+#include "Log.hxx"
+
+#include <unistd.h>
+
+inline void
+UpdateWalk::UpdateSongFile2(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info)
+{
+ db_lock();
+ Song *song = directory.FindSong(name);
+ db_unlock();
+
+ if (!directory_child_access(storage, directory, name, R_OK)) {
+ FormatError(update_domain,
+ "no read permissions on %s/%s",
+ directory.GetPath(), name);
+ if (song != nullptr)
+ editor.LockDeleteSong(directory, song);
+
+ return;
+ }
+
+ if (!(song != nullptr && info.mtime == song->mtime &&
+ !walk_discard) &&
+ UpdateContainerFile(directory, name, suffix, info)) {
+ if (song != nullptr)
+ editor.LockDeleteSong(directory, song);
+
+ return;
+ }
+
+ if (song == nullptr) {
+ FormatDebug(update_domain, "reading %s/%s",
+ directory.GetPath(), name);
+ song = Song::LoadFile(storage, name, directory);
+ if (song == nullptr) {
+ FormatDebug(update_domain,
+ "ignoring unrecognized file %s/%s",
+ directory.GetPath(), name);
+ return;
+ }
+
+ db_lock();
+ directory.AddSong(song);
+ db_unlock();
+
+ modified = true;
+ FormatDefault(update_domain, "added %s/%s",
+ directory.GetPath(), name);
+ } else if (info.mtime != song->mtime || walk_discard) {
+ FormatDefault(update_domain, "updating %s/%s",
+ directory.GetPath(), name);
+ if (!song->UpdateFile(storage)) {
+ FormatDebug(update_domain,
+ "deleting unrecognized file %s/%s",
+ directory.GetPath(), name);
+ editor.LockDeleteSong(directory, song);
+ }
+
+ modified = true;
+ }
+}
+
+bool
+UpdateWalk::UpdateSongFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info)
+{
+ if (!decoder_plugins_supports_suffix(suffix))
+ return false;
+
+ UpdateSongFile2(directory, name, suffix, info);
+ return true;
+}
diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx
new file mode 100644
index 000000000..f71faa86d
--- /dev/null
+++ b/src/db/update/Walk.cxx
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Walk.hxx"
+#include "UpdateIO.hxx"
+#include "Editor.hxx"
+#include "UpdateDomain.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/PlaylistVector.hxx"
+#include "db/Uri.hxx"
+#include "db/plugins/simple/Directory.hxx"
+#include "db/plugins/simple/Song.hxx"
+#include "storage/StorageInterface.hxx"
+#include "playlist/PlaylistRegistry.hxx"
+#include "ExcludeList.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/Traits.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/Charset.hxx"
+#include "storage/FileInfo.hxx"
+#include "util/Alloc.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <memory>
+
+UpdateWalk::UpdateWalk(EventLoop &_loop, DatabaseListener &_listener,
+ Storage &_storage)
+ :cancel(false),
+ storage(_storage),
+ editor(_loop, _listener)
+{
+#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
+}
+
+static void
+directory_set_stat(Directory &dir, const FileInfo &info)
+{
+ dir.inode = info.inode;
+ dir.device = info.device;
+}
+
+inline void
+UpdateWalk::RemoveExcludedFromDirectory(Directory &directory,
+ const ExcludeList &exclude_list)
+{
+ db_lock();
+
+ directory.ForEachChildSafe([&](Directory &child){
+ const auto name_fs =
+ AllocatedPath::FromUTF8(child.GetName());
+
+ if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
+ editor.DeleteDirectory(&child);
+ modified = true;
+ }
+ });
+
+ directory.ForEachSongSafe([&](Song &song){
+ assert(song.parent == &directory);
+
+ const auto name_fs = AllocatedPath::FromUTF8(song.uri);
+ if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
+ editor.DeleteSong(directory, &song);
+ modified = true;
+ }
+ });
+
+ db_unlock();
+}
+
+inline void
+UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
+{
+ directory.ForEachChildSafe([&](Directory &child){
+ if (DirectoryExists(storage, child))
+ return;
+
+ editor.LockDeleteDirectory(&child);
+
+ modified = true;
+ });
+
+ directory.ForEachSongSafe([&](Song &song){
+ if (!directory_child_is_regular(storage, directory,
+ song.uri)) {
+ editor.LockDeleteSong(directory, &song);
+
+ modified = true;
+ }
+ });
+
+ for (auto i = directory.playlists.begin(),
+ end = directory.playlists.end();
+ i != end;) {
+ if (!directory_child_is_regular(storage, directory,
+ i->name.c_str())) {
+ db_lock();
+ i = directory.playlists.erase(i);
+ db_unlock();
+ } else
+ ++i;
+ }
+}
+
+#ifndef WIN32
+static bool
+update_directory_stat(Storage &storage, Directory &directory)
+{
+ FileInfo info;
+ if (!GetInfo(storage, directory.GetPath(), info))
+ return false;
+
+ directory_set_stat(directory, info);
+ return true;
+}
+#endif
+
+/**
+ * Check the ancestors of the given #Directory and see if there's one
+ * with the same device/inode number, building a loop.
+ *
+ * @return 1 if a loop was found, 0 if not, -1 on I/O error
+ */
+static int
+FindAncestorLoop(Storage &storage, Directory *parent,
+ unsigned inode, unsigned device)
+{
+#ifndef WIN32
+ if (device == 0 && inode == 0)
+ /* can't detect loops if the Storage does not support
+ these numbers */
+ return 0;
+
+ while (parent) {
+ if (parent->device == 0 && parent->inode == 0 &&
+ !update_directory_stat(storage, *parent))
+ return -1;
+
+ if (parent->inode == inode && parent->device == device) {
+ LogDebug(update_domain, "recursive directory found");
+ return 1;
+ }
+
+ parent = parent->parent;
+ }
+#else
+ (void)storage;
+ (void)parent;
+ (void)inode;
+ (void)device;
+#endif
+
+ return 0;
+}
+
+inline bool
+UpdateWalk::UpdatePlaylistFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info)
+{
+ if (!playlist_suffix_supported(suffix))
+ return false;
+
+ PlaylistInfo pi(name, info.mtime);
+
+ db_lock();
+ if (directory.playlists.UpdateOrInsert(std::move(pi)))
+ modified = true;
+ db_unlock();
+ return true;
+}
+
+inline bool
+UpdateWalk::UpdateRegularFile(Directory &directory,
+ const char *name, const FileInfo &info)
+{
+ const char *suffix = uri_get_suffix(name);
+ if (suffix == nullptr)
+ return false;
+
+ return UpdateSongFile(directory, name, suffix, info) ||
+ UpdateArchiveFile(directory, name, suffix, info) ||
+ UpdatePlaylistFile(directory, name, suffix, info);
+}
+
+void
+UpdateWalk::UpdateDirectoryChild(Directory &directory,
+ const char *name, const FileInfo &info)
+{
+ assert(strchr(name, '/') == nullptr);
+
+ if (info.IsRegular()) {
+ UpdateRegularFile(directory, name, info);
+ } else if (info.IsDirectory()) {
+ if (FindAncestorLoop(storage, &directory,
+ info.inode, info.device))
+ return;
+
+ db_lock();
+ Directory *subdir = directory.MakeChild(name);
+ db_unlock();
+
+ assert(&directory == subdir->parent);
+
+ if (!UpdateDirectory(*subdir, info))
+ editor.LockDeleteDirectory(subdir);
+ } else {
+ FormatDebug(update_domain,
+ "%s is not a directory, archive or music", name);
+ }
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+gcc_pure
+static bool
+skip_path(const char *name_utf8)
+{
+ return strchr(name_utf8, '\n') != nullptr;
+}
+
+gcc_pure
+bool
+UpdateWalk::SkipSymlink(const Directory *directory,
+ const char *utf8_name) const
+{
+#ifndef WIN32
+ const auto path_fs = storage.MapChildFS(directory->GetPath(),
+ utf8_name);
+ if (path_fs.IsNull())
+ /* not a local file: don't skip */
+ return false;
+
+ const auto 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 (PathTraitsFS::IsAbsolute(target_str)) {
+ /* if the symlink points to an absolute path, see if
+ that path is inside the music directory */
+ const auto target_utf8 = PathToUTF8(target_str);
+ if (target_utf8.empty())
+ return true;
+
+ const char *relative =
+ storage.MapToRelativeUTF8(target_utf8.c_str());
+ return relative != nullptr
+ ? !follow_inside_symlinks
+ : !follow_outside_symlinks;
+ }
+
+ const char *p = target_str;
+ while (*p == '.') {
+ if (p[1] == '.' && PathTraitsFS::IsSeparator(p[2])) {
+ /* "../" moves to parent directory */
+ directory = directory->parent;
+ if (directory == nullptr) {
+ /* we have moved outside the music
+ directory - skip this symlink
+ if such symlinks are not allowed */
+ return !follow_outside_symlinks;
+ }
+ p += 3;
+ } else if (PathTraitsFS::IsSeparator(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
+}
+
+bool
+UpdateWalk::UpdateDirectory(Directory &directory, const FileInfo &info)
+{
+ assert(info.IsDirectory());
+
+ directory_set_stat(directory, info);
+
+ Error error;
+ const std::auto_ptr<StorageDirectoryReader> reader(storage.OpenDirectory(directory.GetPath(), error));
+ if (reader.get() == nullptr) {
+ LogError(error);
+ return false;
+ }
+
+ ExcludeList exclude_list;
+
+ {
+ const auto exclude_path_fs =
+ storage.MapChildFS(directory.GetPath(), ".mpdignore");
+ if (!exclude_path_fs.IsNull())
+ exclude_list.LoadFile(exclude_path_fs);
+ }
+
+ if (!exclude_list.IsEmpty())
+ RemoveExcludedFromDirectory(directory, exclude_list);
+
+ PurgeDeletedFromDirectory(directory);
+
+ const char *name_utf8;
+ while (!cancel && (name_utf8 = reader->Read()) != nullptr) {
+ if (skip_path(name_utf8))
+ continue;
+
+ {
+ const auto name_fs = AllocatedPath::FromUTF8(name_utf8);
+ if (name_fs.IsNull() || exclude_list.Check(name_fs))
+ continue;
+ }
+
+ if (SkipSymlink(&directory, name_utf8)) {
+ modified |= editor.DeleteNameIn(directory, name_utf8);
+ continue;
+ }
+
+ FileInfo info2;
+ if (!GetInfo(*reader, info2)) {
+ modified |= editor.DeleteNameIn(directory, name_utf8);
+ continue;
+ }
+
+ UpdateDirectoryChild(directory, name_utf8, info2);
+ }
+
+ directory.mtime = info.mtime;
+
+ return true;
+}
+
+inline Directory *
+UpdateWalk::DirectoryMakeChildChecked(Directory &parent,
+ const char *uri_utf8,
+ const char *name_utf8)
+{
+ db_lock();
+ Directory *directory = parent.FindChild(name_utf8);
+ db_unlock();
+
+ if (directory != nullptr) {
+ if (directory->IsMount())
+ directory = nullptr;
+
+ return directory;
+ }
+
+ FileInfo info;
+ if (!GetInfo(storage, uri_utf8, info) ||
+ FindAncestorLoop(storage, &parent, info.inode, info.device))
+ return nullptr;
+
+ if (SkipSymlink(&parent, name_utf8))
+ return nullptr;
+
+ /* 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)
+ editor.DeleteSong(parent, conflicting);
+
+ directory = parent.CreateChild(name_utf8);
+ db_unlock();
+
+ directory_set_stat(*directory, info);
+ return directory;
+}
+
+inline Directory *
+UpdateWalk::DirectoryMakeUriParentChecked(Directory &root, const char *uri)
+{
+ Directory *directory = &root;
+ char *duplicated = xstrdup(uri);
+ char *name_utf8 = duplicated, *slash;
+
+ while ((slash = strchr(name_utf8, '/')) != nullptr) {
+ *slash = 0;
+
+ if (*name_utf8 == 0)
+ continue;
+
+ directory = DirectoryMakeChildChecked(*directory,
+ duplicated,
+ name_utf8);
+ if (directory == nullptr)
+ break;
+
+ name_utf8 = slash + 1;
+ }
+
+ free(duplicated);
+ return directory;
+}
+
+inline void
+UpdateWalk::UpdateUri(Directory &root, const char *uri)
+{
+ Directory *parent = DirectoryMakeUriParentChecked(root, uri);
+ if (parent == nullptr)
+ return;
+
+ const char *name = PathTraitsUTF8::GetBase(uri);
+
+ if (SkipSymlink(parent, name)) {
+ modified |= editor.DeleteNameIn(*parent, name);
+ return;
+ }
+
+ FileInfo info;
+ if (!GetInfo(storage, uri, info)) {
+ modified |= editor.DeleteNameIn(*parent, name);
+ return;
+ }
+
+ UpdateDirectoryChild(*parent, name, info);
+}
+
+bool
+UpdateWalk::Walk(Directory &root, const char *path, bool discard)
+{
+ walk_discard = discard;
+ modified = false;
+
+ if (path != nullptr && !isRootDirectory(path)) {
+ UpdateUri(root, path);
+ } else {
+ FileInfo info;
+ if (!GetInfo(storage, "", info))
+ return false;
+
+ UpdateDirectory(root, info);
+ }
+
+ return modified;
+}
diff --git a/src/db/update/Walk.hxx b/src/db/update/Walk.hxx
new file mode 100644
index 000000000..a4c518813
--- /dev/null
+++ b/src/db/update/Walk.hxx
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPDATE_WALK_HXX
+#define MPD_UPDATE_WALK_HXX
+
+#include "check.h"
+#include "Editor.hxx"
+#include "Compiler.h"
+
+#include <sys/stat.h>
+
+struct stat;
+struct FileInfo;
+struct Directory;
+struct ArchivePlugin;
+class Storage;
+class ExcludeList;
+
+class UpdateWalk final {
+#ifdef ENABLE_ARCHIVE
+ friend class UpdateArchiveVisitor;
+#endif
+
+#ifndef WIN32
+ static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true;
+ static constexpr bool DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true;
+
+ bool follow_inside_symlinks;
+ bool follow_outside_symlinks;
+#endif
+
+ bool walk_discard;
+ bool modified;
+
+ /**
+ * Set to true by the main thread when the update thread shall
+ * cancel as quickly as possible. Access to this flag is
+ * unprotected.
+ */
+ volatile bool cancel;
+
+ Storage &storage;
+
+ DatabaseEditor editor;
+
+public:
+ UpdateWalk(EventLoop &_loop, DatabaseListener &_listener,
+ Storage &_storage);
+
+ /**
+ * Cancel the current update and quit the Walk() method as
+ * soon as possible.
+ */
+ void Cancel() {
+ cancel = true;
+ }
+
+ /**
+ * Returns true if the database was modified.
+ */
+ bool Walk(Directory &root, const char *path, bool discard);
+
+private:
+ gcc_pure
+ bool SkipSymlink(const Directory *directory,
+ const char *utf8_name) const;
+
+ void RemoveExcludedFromDirectory(Directory &directory,
+ const ExcludeList &exclude_list);
+
+ void PurgeDeletedFromDirectory(Directory &directory);
+
+ void UpdateSongFile2(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info);
+
+ bool UpdateSongFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info);
+
+ bool UpdateContainerFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info);
+
+
+#ifdef ENABLE_ARCHIVE
+ void UpdateArchiveTree(Directory &parent, const char *name);
+
+ bool UpdateArchiveFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info);
+
+ void UpdateArchiveFile(Directory &directory, const char *name,
+ const FileInfo &info,
+ const ArchivePlugin &plugin);
+
+
+#else
+ bool UpdateArchiveFile(gcc_unused Directory &directory,
+ gcc_unused const char *name,
+ gcc_unused const char *suffix,
+ gcc_unused const FileInfo &info) {
+ return false;
+ }
+#endif
+
+ bool UpdatePlaylistFile(Directory &directory,
+ const char *name, const char *suffix,
+ const FileInfo &info);
+
+ bool UpdateRegularFile(Directory &directory,
+ const char *name, const FileInfo &info);
+
+ void UpdateDirectoryChild(Directory &directory,
+ const char *name, const FileInfo &info);
+
+ bool UpdateDirectory(Directory &directory, const FileInfo &info);
+
+ /**
+ * 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 nullptr when it
+ * exists already and is unmodified.
+ *
+ * The caller must lock the database.
+ */
+ Directory *MakeDirectoryIfModified(Directory &parent, const char *name,
+ const FileInfo &info);
+
+ Directory *DirectoryMakeChildChecked(Directory &parent,
+ const char *uri_utf8,
+ const char *name_utf8);
+
+ Directory *DirectoryMakeUriParentChecked(Directory &root,
+ const char *uri);
+
+ void UpdateUri(Directory &root, const char *uri);
+};
+
+#endif
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx
deleted file mode 100644
index c79fca5f9..000000000
--- a/src/decoder/AdPlugDecoderPlugin.cxx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AdPlugDecoderPlugin.h"
-#include "tag/TagHandler.hxx"
-#include "DecoderAPI.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/Error.hxx"
-#include "util/Macros.hxx"
-#include "Log.hxx"
-
-#include <adplug/adplug.h>
-#include <adplug/emuopl.h>
-
-#include <assert.h>
-
-static unsigned sample_rate;
-
-static bool
-adplug_init(const config_param &param)
-{
- Error error;
-
- sample_rate = param.GetBlockValue("sample_rate", 48000u);
- if (!audio_check_sample_rate(sample_rate, error)) {
- LogError(error);
- return false;
- }
-
- return true;
-}
-
-static void
-adplug_file_decode(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 = ARRAY_SIZE(buffer) / 2;
- DecoderCommand cmd;
-
- do {
- if (!player->update())
- break;
-
- opl.update(buffer, frames_per_buffer);
- cmd = decoder_data(decoder, nullptr,
- buffer, sizeof(buffer),
- 0);
- } while (cmd == DecoderCommand::NONE);
-
- delete player;
-}
-
-static void
-adplug_scan_tag(TagType 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 DecoderPlugin 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
deleted file mode 100644
index a827fdc7d..000000000
--- a/src/decoder/AdPlugDecoderPlugin.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_ADPLUG_H
-#define MPD_DECODER_ADPLUG_H
-
-extern const struct DecoderPlugin adplug_decoder_plugin;
-
-#endif
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx
deleted file mode 100644
index b1b8bf613..000000000
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AudiofileDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <audiofile.h>
-#include <af_vfs.h>
-
-#include <assert.h>
-#include <stdio.h>
-
-/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
-#define CHUNK_SIZE 1020
-
-static constexpr Domain audiofile_domain("audiofile");
-
-struct AudioFileInputStream {
- Decoder *const decoder;
- InputStream &is;
-
- size_t Read(void *buffer, size_t size) {
- /* libaudiofile does not like partial reads at all,
- and will abort playback; therefore always force full
- reads */
- return decoder_read_full(decoder, is, buffer, size)
- ? size
- : 0;
- }
-};
-
-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)
-{
- AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
-
- return afis.Read(data, length);
-}
-
-static AFfileoffset
-audiofile_file_length(AFvirtualfile *vfile)
-{
- AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
- InputStream &is = afis.is;
-
- return is.GetSize();
-}
-
-static AFfileoffset
-audiofile_file_tell(AFvirtualfile *vfile)
-{
- AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
- InputStream &is = afis.is;
-
- return is.GetOffset();
-}
-
-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)
-{
- AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
- InputStream &is = afis.is;
-
- int whence = (is_relative ? SEEK_CUR : SEEK_SET);
-
- Error error;
- if (is.LockSeek(offset, whence, error)) {
- return is.GetOffset();
- } else {
- LogError(error, "Seek failed");
- return -1;
- }
-}
-
-static AFvirtualfile *
-setup_virtual_fops(AudioFileInputStream &afis)
-{
- AFvirtualfile *vf = new AFvirtualfile();
- vf->closure = &afis;
- 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))) {
- FormatDebug(audiofile_domain,
- "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(Decoder &decoder, InputStream &is)
-{
- 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];
-
- if (!is.IsSeekable()) {
- LogWarning(audiofile_domain, "not seekable");
- return;
- }
-
- AudioFileInputStream afis{&decoder, is};
- vf = setup_virtual_fops(afis);
-
- af_fp = afOpenVirtualFile(vf, "r", nullptr);
- if (af_fp == AF_NULL_FILEHANDLE) {
- LogWarning(audiofile_domain, "failed to input stream");
- return;
- }
-
- Error error;
- 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)) {
- LogError(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.GetSize() * 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);
-
- DecoderCommand cmd;
- 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 == DecoderCommand::SEEK) {
- AFframecount frame = decoder_seek_where(decoder) *
- audio_format.sample_rate;
- afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
-
- decoder_command_finished(decoder);
- cmd = DecoderCommand::NONE;
- }
- } while (cmd == DecoderCommand::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) {
- FormatWarning(audiofile_domain,
- "Failed to get total song time from: %s",
- 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 DecoderPlugin 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
deleted file mode 100644
index 5a17281b0..000000000
--- a/src/decoder/AudiofileDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_AUDIOFILE_HXX
-#define MPD_DECODER_AUDIOFILE_HXX
-
-extern const struct DecoderPlugin audiofile_decoder_plugin;
-
-#endif
diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx
new file mode 100644
index 000000000..4794d60e7
--- /dev/null
+++ b/src/decoder/DecoderAPI.cxx
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderAPI.hxx"
+#include "DecoderError.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "AudioConfig.hxx"
+#include "ReplayGainConfig.hxx"
+#include "MusicChunk.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "DecoderControl.hxx"
+#include "DecoderInternal.hxx"
+#include "DetachedSong.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+void
+decoder_initialized(Decoder &decoder,
+ const AudioFormat audio_format,
+ bool seekable, SignedSongTime duration)
+{
+ DecoderControl &dc = decoder.dc;
+ struct audio_format_string af_string;
+
+ assert(dc.state == DecoderState::START);
+ assert(dc.pipe != nullptr);
+ assert(dc.pipe->IsEmpty());
+ assert(decoder.convert == nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ 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 = duration;
+
+ FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
+ audio_format_to_string(dc.in_audio_format, &af_string),
+ seekable ? "true" : "false");
+
+ if (dc.in_audio_format != dc.out_audio_format) {
+ FormatDebug(decoder_domain, "converting to %s",
+ audio_format_to_string(dc.out_audio_format,
+ &af_string));
+
+ decoder.convert = new PcmConvert();
+
+ Error error;
+ if (!decoder.convert->Open(dc.in_audio_format,
+ dc.out_audio_format,
+ error))
+ decoder.error = std::move(error);
+ }
+
+ dc.Lock();
+ dc.state = DecoderState::DECODE;
+ dc.client_cond.signal();
+ dc.Unlock();
+}
+
+/**
+ * Checks if we need an "initial seek". If so, then the initial seek
+ * is prepared, and the function returns true.
+ */
+gcc_pure
+static bool
+decoder_prepare_initial_seek(Decoder &decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+ assert(dc.pipe != nullptr);
+
+ if (dc.state != DecoderState::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 == DecoderCommand::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.
+ */
+gcc_pure
+static DecoderCommand
+decoder_get_virtual_command(Decoder &decoder)
+{
+ if (decoder.error.IsDefined())
+ /* an error has occurred: stop the decoder plugin */
+ return DecoderCommand::STOP;
+
+ const DecoderControl &dc = decoder.dc;
+ assert(dc.pipe != nullptr);
+
+ if (decoder_prepare_initial_seek(decoder))
+ return DecoderCommand::SEEK;
+
+ return dc.command;
+}
+
+DecoderCommand
+decoder_get_command(Decoder &decoder)
+{
+ return decoder_get_virtual_command(decoder);
+}
+
+void
+decoder_command_finished(Decoder &decoder)
+{
+ DecoderControl &dc = decoder.dc;
+
+ dc.Lock();
+
+ assert(dc.command != DecoderCommand::NONE ||
+ decoder.initial_seek_running);
+ assert(dc.command != DecoderCommand::SEEK ||
+ decoder.initial_seek_running ||
+ dc.seek_error || decoder.seeking);
+ assert(dc.pipe != nullptr);
+
+ if (decoder.initial_seek_running) {
+ assert(!decoder.seeking);
+ assert(decoder.chunk == nullptr);
+ assert(dc.pipe->IsEmpty());
+
+ decoder.initial_seek_running = false;
+ decoder.timestamp = dc.start_time.ToDoubleS();
+ dc.Unlock();
+ return;
+ }
+
+ if (decoder.seeking) {
+ decoder.seeking = false;
+
+ /* delete frames from the old song position */
+
+ if (decoder.chunk != nullptr) {
+ dc.buffer->Return(decoder.chunk);
+ decoder.chunk = nullptr;
+ }
+
+ dc.pipe->Clear(*dc.buffer);
+
+ decoder.timestamp = dc.seek_time.ToDoubleS();
+ }
+
+ dc.command = DecoderCommand::NONE;
+ dc.client_cond.signal();
+ dc.Unlock();
+}
+
+SongTime
+decoder_seek_time(Decoder &decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+
+ assert(dc.pipe != nullptr);
+
+ if (decoder.initial_seek_running)
+ return dc.start_time;
+
+ assert(dc.command == DecoderCommand::SEEK);
+
+ decoder.seeking = true;
+
+ return dc.seek_time;
+}
+
+uint64_t
+decoder_seek_where_frame(Decoder &decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+
+ return decoder_seek_time(decoder).ToScale<uint64_t>(dc.in_audio_format.sample_rate);
+}
+
+void decoder_seek_error(Decoder & decoder)
+{
+ DecoderControl &dc = decoder.dc;
+
+ assert(dc.pipe != nullptr);
+
+ 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 == DecoderCommand::SEEK);
+
+ dc.seek_error = true;
+ decoder.seeking = false;
+
+ decoder_command_finished(decoder);
+}
+
+InputStream *
+decoder_open_uri(Decoder &decoder, const char *uri, Error &error)
+{
+ assert(decoder.dc.state == DecoderState::START ||
+ decoder.dc.state == DecoderState::DECODE);
+
+ DecoderControl &dc = decoder.dc;
+ Mutex &mutex = dc.mutex;
+ Cond &cond = dc.cond;
+
+ InputStream *is = InputStream::Open(uri, mutex, cond, error);
+ if (is == nullptr)
+ return nullptr;
+
+ mutex.lock();
+ while (true) {
+ is->Update();
+ if (is->IsReady()) {
+ mutex.unlock();
+ return is;
+ }
+
+ if (dc.command == DecoderCommand::STOP) {
+ mutex.unlock();
+ delete is;
+ return nullptr;
+ }
+
+ cond.wait(mutex);
+ }
+}
+
+/**
+ * Should be read operation be cancelled? That is the case when the
+ * player thread has sent a command such as "STOP".
+ */
+gcc_pure
+static inline bool
+decoder_check_cancel_read(const Decoder *decoder)
+{
+ if (decoder == nullptr)
+ return false;
+
+ const DecoderControl &dc = decoder->dc;
+ if (dc.command == DecoderCommand::NONE)
+ return false;
+
+ /* ignore the SEEK command during initialization, the plugin
+ should handle that after it has initialized successfully */
+ if (dc.command == DecoderCommand::SEEK &&
+ (dc.state == DecoderState::START || decoder->seeking))
+ return false;
+
+ return true;
+}
+
+size_t
+decoder_read(Decoder *decoder,
+ InputStream &is,
+ void *buffer, size_t length)
+{
+ /* XXX don't allow decoder==nullptr */
+
+ assert(decoder == nullptr ||
+ decoder->dc.state == DecoderState::START ||
+ decoder->dc.state == DecoderState::DECODE);
+ assert(buffer != nullptr);
+
+ if (length == 0)
+ return 0;
+
+ is.Lock();
+
+ while (true) {
+ if (decoder_check_cancel_read(decoder)) {
+ is.Unlock();
+ return 0;
+ }
+
+ if (is.IsAvailable())
+ break;
+
+ is.cond.wait(is.mutex);
+ }
+
+ Error error;
+ size_t nbytes = is.Read(buffer, length, error);
+ assert(nbytes == 0 || !error.IsDefined());
+ assert(nbytes > 0 || error.IsDefined() || is.IsEOF());
+
+ is.Unlock();
+
+ if (gcc_unlikely(nbytes == 0 && error.IsDefined()))
+ LogError(error);
+
+ return nbytes;
+}
+
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *_buffer, size_t size)
+{
+ uint8_t *buffer = (uint8_t *)_buffer;
+
+ while (size > 0) {
+ size_t nbytes = decoder_read(decoder, is, buffer, size);
+ if (nbytes == 0)
+ return false;
+
+ buffer += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+ while (size > 0) {
+ char buffer[1024];
+ size_t nbytes = decoder_read(decoder, is, buffer,
+ std::min(sizeof(buffer), size));
+ if (nbytes == 0)
+ return false;
+
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+void
+decoder_timestamp(Decoder &decoder, double t)
+{
+ 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 DecoderCommand
+do_send_tag(Decoder &decoder, const Tag &tag)
+{
+ MusicChunk *chunk;
+
+ if (decoder.chunk != nullptr) {
+ /* there is a partial chunk - flush it, we want the
+ tag in a new chunk */
+ decoder.FlushChunk();
+ }
+
+ assert(decoder.chunk == nullptr);
+
+ chunk = decoder.GetChunk();
+ if (chunk == nullptr) {
+ assert(decoder.dc.command != DecoderCommand::NONE);
+ return decoder.dc.command;
+ }
+
+ chunk->tag = new Tag(tag);
+ return DecoderCommand::NONE;
+}
+
+static bool
+update_stream_tag(Decoder &decoder, InputStream *is)
+{
+ Tag *tag;
+
+ tag = is != nullptr
+ ? is->LockReadTag()
+ : nullptr;
+ if (tag == nullptr) {
+ tag = decoder.song_tag;
+ if (tag == nullptr)
+ return false;
+
+ /* no stream tag present - submit the song tag
+ instead */
+ decoder.song_tag = nullptr;
+ }
+
+ delete decoder.stream_tag;
+ decoder.stream_tag = tag;
+ return true;
+}
+
+DecoderCommand
+decoder_data(Decoder &decoder,
+ InputStream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate)
+{
+ DecoderControl &dc = decoder.dc;
+ DecoderCommand cmd;
+
+ assert(dc.state == DecoderState::DECODE);
+ assert(dc.pipe != nullptr);
+ assert(length % dc.in_audio_format.GetFrameSize() == 0);
+
+ dc.Lock();
+ cmd = decoder_get_virtual_command(decoder);
+ dc.Unlock();
+
+ if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK ||
+ length == 0)
+ return cmd;
+
+ assert(!decoder.initial_seek_pending);
+ assert(!decoder.initial_seek_running);
+
+ /* send stream tags */
+
+ if (update_stream_tag(decoder, is)) {
+ if (decoder.decoder_tag != nullptr) {
+ /* 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 != DecoderCommand::NONE)
+ return cmd;
+ }
+
+ if (decoder.convert != nullptr) {
+ assert(dc.in_audio_format != dc.out_audio_format);
+
+ Error error;
+ auto result = decoder.convert->Convert({data, length},
+ error);
+ if (data == nullptr) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ LogError(error);
+ return DecoderCommand::STOP;
+ }
+
+ data = result.data;
+ length = result.size;
+ } else {
+ assert(dc.in_audio_format == dc.out_audio_format);
+ }
+
+ while (length > 0) {
+ MusicChunk *chunk;
+ bool full;
+
+ chunk = decoder.GetChunk();
+ if (chunk == nullptr) {
+ assert(dc.command != DecoderCommand::NONE);
+ return dc.command;
+ }
+
+ const auto dest =
+ chunk->Write(dc.out_audio_format,
+ SongTime::FromS(decoder.timestamp) -
+ dc.song->GetStartTime(),
+ kbit_rate);
+ if (dest.IsEmpty()) {
+ /* the chunk is full, flush it */
+ decoder.FlushChunk();
+ continue;
+ }
+
+ const size_t nbytes = std::min(dest.size, length);
+
+ /* copy the buffer */
+
+ memcpy(dest.data, data, nbytes);
+
+ /* expand the music pipe chunk */
+
+ full = chunk->Expand(dc.out_audio_format, nbytes);
+ if (full) {
+ /* the chunk is full, flush it */
+ decoder.FlushChunk();
+ }
+
+ data = (const uint8_t *)data + nbytes;
+ length -= nbytes;
+
+ decoder.timestamp += (double)nbytes /
+ dc.out_audio_format.GetTimeToSize();
+
+ if (dc.end_time.IsPositive() &&
+ decoder.timestamp >= dc.end_time.ToDoubleS())
+ /* the end of this range has been reached:
+ stop decoding */
+ return DecoderCommand::STOP;
+ }
+
+ return DecoderCommand::NONE;
+}
+
+DecoderCommand
+decoder_tag(Decoder &decoder, InputStream *is,
+ Tag &&tag)
+{
+ gcc_unused const DecoderControl &dc = decoder.dc;
+ DecoderCommand cmd;
+
+ assert(dc.state == DecoderState::DECODE);
+ assert(dc.pipe != nullptr);
+
+ /* 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 DecoderCommand::SEEK;
+
+ /* send tag to music pipe */
+
+ if (decoder.stream_tag != nullptr) {
+ /* 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(Decoder &decoder,
+ const ReplayGainInfo *replay_gain_info)
+{
+ if (replay_gain_info != nullptr) {
+ static unsigned serial;
+ if (++serial == 0)
+ serial = 1;
+
+ if (REPLAY_GAIN_OFF != replay_gain_mode) {
+ ReplayGainMode rgm = replay_gain_mode;
+ if (rgm != REPLAY_GAIN_ALBUM)
+ rgm = REPLAY_GAIN_TRACK;
+
+ const auto &tuple = replay_gain_info->tuples[rgm];
+ const auto scale =
+ tuple.CalculateScale(replay_gain_preamp,
+ replay_gain_missing_preamp,
+ replay_gain_limit);
+ decoder.dc.replay_gain_db = 20.0 * log10f(scale);
+ }
+
+ decoder.replay_gain_info = *replay_gain_info;
+ decoder.replay_gain_serial = serial;
+
+ if (decoder.chunk != nullptr) {
+ /* flush the current chunk because the new
+ replay gain values affect the following
+ samples */
+ decoder.FlushChunk();
+ }
+ } else
+ decoder.replay_gain_serial = 0;
+}
+
+void
+decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp)
+{
+ DecoderControl &dc = decoder.dc;
+
+ dc.SetMixRamp(std::move(mix_ramp));
+}
diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx
new file mode 100644
index 000000000..b756331d9
--- /dev/null
+++ b/src/decoder/DecoderAPI.hxx
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \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
+
+// IWYU pragma: begin_exports
+
+#include "check.h"
+#include "DecoderCommand.hxx"
+#include "DecoderPlugin.hxx"
+#include "ReplayGainInfo.hxx"
+#include "tag/Tag.hxx"
+#include "AudioFormat.hxx"
+#include "MixRampInfo.hxx"
+#include "config/ConfigData.hxx"
+#include "Chrono.hxx"
+
+// IWYU pragma: end_exports
+
+#include <stdint.h>
+
+class Error;
+
+/**
+ * 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 duration the total duration of this song; negative if
+ * unknown
+ */
+void
+decoder_initialized(Decoder &decoder,
+ AudioFormat audio_format,
+ bool seekable, SignedSongTime duration);
+
+/**
+ * Determines the pending decoder command.
+ *
+ * @param decoder the decoder object
+ * @return the current command, or DecoderCommand::NONE if there is no
+ * command pending
+ */
+gcc_pure
+DecoderCommand
+decoder_get_command(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(Decoder &decoder);
+
+/**
+ * Call this when you have received the DecoderCommand::SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the seek in milliseconds
+ */
+gcc_pure
+SongTime
+decoder_seek_time(Decoder &decoder);
+
+/**
+ * Call this when you have received the DecoderCommand::SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the seek in frames
+ */
+gcc_pure
+uint64_t
+decoder_seek_where_frame(Decoder &decoder);
+
+/**
+ * Call this instead of decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(Decoder &decoder);
+
+/**
+ * Open a new #InputStream and wait until it's ready. Can get
+ * cancelled by DecoderCommand::STOP (returns nullptr without setting
+ * #Error).
+ */
+InputStream *
+decoder_open_uri(Decoder &decoder, const char *uri, Error &error);
+
+/**
+ * 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(Decoder *decoder, InputStream &is,
+ void *buffer, size_t length);
+
+static inline size_t
+decoder_read(Decoder &decoder, InputStream &is,
+ void *buffer, size_t length)
+{
+ return decoder_read(&decoder, is, buffer, length);
+}
+
+/**
+ * Blocking read from the input stream. Attempts to fill the buffer
+ * completely; there is no partial result.
+ *
+ * @return true on success, false on error or command or not enough
+ * data
+ */
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *buffer, size_t size);
+
+/**
+ * Skip data on the #InputStream.
+ *
+ * @return true on success, false on error or command
+ */
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size);
+
+/**
+ * Sets the time stamp for the next data chunk [seconds]. The MPD
+ * core automatically counts it up, and a decoder plugin only needs to
+ * use this function if it thinks that adding to the time stamp based
+ * on the buffer size won't work.
+ */
+void
+decoder_timestamp(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 DecoderCommand::NONE if there is no
+ * command pending
+ */
+DecoderCommand
+decoder_data(Decoder &decoder, InputStream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate);
+
+static inline DecoderCommand
+decoder_data(Decoder &decoder, InputStream &is,
+ const void *data, size_t length,
+ uint16_t kbit_rate)
+{
+ return decoder_data(decoder, &is, data, length, 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 DecoderCommand::NONE if there is no
+ * command pending
+ */
+DecoderCommand
+decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag);
+
+static inline DecoderCommand
+decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag)
+{
+ return decoder_tag(decoder, &is, std::move(tag));
+}
+
+/**
+ * Set replay gain values for the following chunks.
+ *
+ * @param decoder the decoder object
+ * @param rgi the replay_gain_info object; may be nullptr to invalidate
+ * the previous replay gain values
+ */
+void
+decoder_replay_gain(Decoder &decoder,
+ const ReplayGainInfo *replay_gain_info);
+
+/**
+ * Store MixRamp tags.
+ *
+ * @param decoder the decoder object
+ * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate
+ * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate
+ */
+void
+decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp);
+
+#endif
diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx
new file mode 100644
index 000000000..a8958d6fd
--- /dev/null
+++ b/src/decoder/DecoderBuffer.cxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderBuffer.hxx"
+#include "DecoderAPI.hxx"
+
+#include <assert.h>
+
+bool
+DecoderBuffer::Fill()
+{
+ auto w = buffer.Write();
+ if (w.IsEmpty())
+ /* buffer is full */
+ return false;
+
+ size_t nbytes = decoder_read(decoder, is,
+ w.data, w.size);
+ if (nbytes == 0)
+ /* end of file, I/O error or decoder command
+ received */
+ return false;
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+ConstBuffer<void>
+DecoderBuffer::Need(size_t min_size)
+{
+ while (true) {
+ const auto r = Read();
+ if (r.size >= min_size)
+ return r;
+
+ if (!Fill())
+ return nullptr;
+ }
+}
+
+bool
+DecoderBuffer::Skip(size_t nbytes)
+{
+ const auto r = buffer.Read();
+ if (r.size >= nbytes) {
+ buffer.Consume(nbytes);
+ return true;
+ }
+
+ buffer.Clear();
+ nbytes -= r.size;
+
+ return decoder_skip(decoder, is, nbytes);
+}
diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx
new file mode 100644
index 000000000..9cf47d915
--- /dev/null
+++ b/src/decoder/DecoderBuffer.hxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_BUFFER_HXX
+#define MPD_DECODER_BUFFER_HXX
+
+#include "Compiler.h"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <stddef.h>
+
+struct Decoder;
+class InputStream;
+
+/**
+ * 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.
+ */
+class DecoderBuffer {
+ Decoder *const decoder;
+ InputStream &is;
+
+ DynamicFifoBuffer<uint8_t> buffer;
+
+public:
+ /**
+ * Creates a new buffer.
+ *
+ * @param _decoder the decoder object, used for decoder_read(),
+ * may be nullptr
+ * @param _is the input stream object where we should read from
+ * @param _size the maximum size of the buffer
+ */
+ DecoderBuffer(Decoder *_decoder, InputStream &_is,
+ size_t _size)
+ :decoder(_decoder), is(_is), buffer(_size) {}
+
+ const InputStream &GetStream() const {
+ return is;
+ }
+
+ void Clear() {
+ buffer.Clear();
+ }
+
+ /**
+ * Read data from the #InputStream 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 Fill();
+
+ /**
+ * How many bytes are stored in the buffer?
+ */
+ gcc_pure
+ size_t GetAvailable() const {
+ return buffer.GetAvailable();
+ }
+
+ /**
+ * Reads data from the buffer. This data is not yet consumed,
+ * you have to call Consume() to do that. The returned buffer
+ * becomes invalid after a Fill() or a Consume() call.
+ */
+ ConstBuffer<void> Read() const {
+ auto r = buffer.Read();
+ return { r.data, r.size };
+ }
+
+ /**
+ * Wait until this number of bytes are available. Returns nullptr on
+ * error.
+ */
+ ConstBuffer<void> Need(size_t min_size);
+
+ /**
+ * Consume (delete, invalidate) a part of the buffer. The
+ * "nbytes" parameter must not be larger than the length
+ * returned by Read().
+ *
+ * @param nbytes the number of bytes to consume
+ */
+ void Consume(size_t nbytes) {
+ buffer.Consume(nbytes);
+ }
+
+ /**
+ * Skips the specified number of bytes, discarding its data.
+ *
+ * @param nbytes the number of bytes to skip
+ * @return true on success, false on error
+ */
+ bool Skip(size_t nbytes);
+};
+
+#endif
diff --git a/src/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx
new file mode 100644
index 000000000..a00519644
--- /dev/null
+++ b/src/decoder/DecoderCommand.hxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_COMMAND_HXX
+#define MPD_DECODER_COMMAND_HXX
+
+#include <stdint.h>
+
+enum class DecoderCommand : uint8_t {
+ NONE = 0,
+ START,
+ STOP,
+ SEEK
+};
+
+#endif
diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx
new file mode 100644
index 000000000..c30da6214
--- /dev/null
+++ b/src/decoder/DecoderControl.cxx
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "DetachedSong.hxx"
+
+#include <assert.h>
+
+DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond)
+ :mutex(_mutex), client_cond(_client_cond),
+ state(DecoderState::STOP),
+ command(DecoderCommand::NONE),
+ client_is_waiting(false),
+ song(nullptr),
+ replay_gain_db(0), replay_gain_prev_db(0) {}
+
+DecoderControl::~DecoderControl()
+{
+ ClearError();
+
+ delete song;
+}
+
+void
+DecoderControl::WaitForDecoder()
+{
+ assert(!client_is_waiting);
+ client_is_waiting = true;
+
+ client_cond.wait(mutex);
+
+ assert(client_is_waiting);
+ client_is_waiting = false;
+}
+
+bool
+DecoderControl::IsCurrentSong(const DetachedSong &_song) const
+{
+ switch (state) {
+ case DecoderState::STOP:
+ case DecoderState::ERROR:
+ return false;
+
+ case DecoderState::START:
+ case DecoderState::DECODE:
+ return song->IsSame(_song);
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+void
+DecoderControl::Start(DetachedSong *_song,
+ SongTime _start_time, SongTime _end_time,
+ MusicBuffer &_buffer, MusicPipe &_pipe)
+{
+ assert(_song != nullptr);
+ assert(_pipe.IsEmpty());
+
+ delete song;
+ song = _song;
+ start_time = _start_time;
+ end_time = _end_time;
+ buffer = &_buffer;
+ pipe = &_pipe;
+
+ LockSynchronousCommand(DecoderCommand::START);
+}
+
+void
+DecoderControl::Stop()
+{
+ Lock();
+
+ if (command != DecoderCommand::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). */
+ SynchronousCommandLocked(DecoderCommand::STOP);
+
+ if (state != DecoderState::STOP && state != DecoderState::ERROR)
+ SynchronousCommandLocked(DecoderCommand::STOP);
+
+ Unlock();
+}
+
+bool
+DecoderControl::Seek(SongTime t)
+{
+ assert(state != DecoderState::START);
+
+ if (state == DecoderState::STOP ||
+ state == DecoderState::ERROR || !seekable)
+ return false;
+
+ seek_time = t;
+ seek_error = false;
+ LockSynchronousCommand(DecoderCommand::SEEK);
+
+ return !seek_error;
+}
+
+void
+DecoderControl::Quit()
+{
+ assert(thread.IsDefined());
+
+ quit = true;
+ LockAsynchronousCommand(DecoderCommand::STOP);
+
+ thread.Join();
+}
+
+void
+DecoderControl::CycleMixRamp()
+{
+ previous_mix_ramp = std::move(mix_ramp);
+ mix_ramp.Clear();
+}
diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx
new file mode 100644
index 000000000..ed2b8c538
--- /dev/null
+++ b/src/decoder/DecoderControl.hxx
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_CONTROL_HXX
+#define MPD_DECODER_CONTROL_HXX
+
+#include "DecoderCommand.hxx"
+#include "AudioFormat.hxx"
+#include "MixRampInfo.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "Chrono.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+#include <stdint.h>
+
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+
+class DetachedSong;
+class MusicBuffer;
+class MusicPipe;
+
+enum class DecoderState : uint8_t {
+ STOP = 0,
+ START,
+ 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.
+ */
+ ERROR,
+};
+
+struct DecoderControl {
+ /**
+ * The handle of the decoder thread.
+ */
+ Thread thread;
+
+ /**
+ * This lock protects #state and #command.
+ *
+ * This is usually a reference to PlayerControl::mutex, so
+ * that both player thread and decoder thread share a mutex.
+ * This simplifies synchronization with #cond and
+ * #client_cond.
+ */
+ 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.
+ *
+ * This is usually a reference to PlayerControl::cond.
+ */
+ Cond &client_cond;
+
+ DecoderState state;
+ DecoderCommand command;
+
+ /**
+ * The error that occurred in the decoder thread. This
+ * attribute is only valid if #state is #DecoderState::ERROR.
+ * The object must be freed when this object transitions to
+ * any other state (usually #DecoderState::START).
+ */
+ Error error;
+
+ bool quit;
+
+ /**
+ * Is the client currently waiting for the DecoderThread? If
+ * false, the DecoderThread may omit invoking Cond::signal(),
+ * reducing the number of system calls.
+ */
+ bool client_is_waiting;
+
+ bool seek_error;
+ bool seekable;
+ SongTime seek_time;
+
+ /** 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 #DecoderCommand::START
+ * command.
+ *
+ * This is a duplicate, and must be freed when this attribute
+ * is cleared.
+ */
+ DetachedSong *song;
+
+ /**
+ * The initial seek position, e.g. to the start of a sub-track
+ * described by a CUE file.
+ *
+ * This attribute is set by Start().
+ */
+ SongTime start_time;
+
+ /**
+ * The decoder will stop when it reaches this position. 0
+ * means don't stop before the end of the file.
+ *
+ * This attribute is set by Start().
+ */
+ SongTime end_time;
+
+ SignedSongTime total_time;
+
+ /** the #MusicChunk allocator */
+ MusicBuffer *buffer;
+
+ /**
+ * The destination pipe for decoded chunks. The caller thread
+ * owns this object, and is responsible for freeing it.
+ */
+ MusicPipe *pipe;
+
+ float replay_gain_db;
+ float replay_gain_prev_db;
+
+ MixRampInfo mix_ramp, previous_mix_ramp;
+
+ /**
+ * @param _mutex see #mutex
+ * @param _client_cond see #client_cond
+ */
+ DecoderControl(Mutex &_mutex, Cond &_client_cond);
+ ~DecoderControl();
+
+ /**
+ * 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 #DecoderControl 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.
+ *
+ * Caller must hold the lock.
+ */
+ void WaitForDecoder();
+
+ bool IsIdle() const {
+ return state == DecoderState::STOP ||
+ state == DecoderState::ERROR;
+ }
+
+ gcc_pure
+ bool LockIsIdle() const {
+ Lock();
+ bool result = IsIdle();
+ Unlock();
+ return result;
+ }
+
+ bool IsStarting() const {
+ return state == DecoderState::START;
+ }
+
+ gcc_pure
+ bool LockIsStarting() const {
+ Lock();
+ bool result = IsStarting();
+ Unlock();
+ return result;
+ }
+
+ bool HasFailed() const {
+ assert(command == DecoderCommand::NONE);
+
+ return state == DecoderState::ERROR;
+ }
+
+ gcc_pure
+ bool LockHasFailed() const {
+ Lock();
+ bool result = HasFailed();
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Checks whether an error has occurred, and if so, returns a
+ * copy of the #Error object.
+ *
+ * Caller must lock the object.
+ */
+ gcc_pure
+ Error GetError() const {
+ assert(command == DecoderCommand::NONE);
+ assert(state != DecoderState::ERROR || error.IsDefined());
+
+ Error result;
+ if (state == DecoderState::ERROR)
+ result.Set(error);
+ return result;
+ }
+
+ /**
+ * Like GetError(), but locks and unlocks the object.
+ */
+ gcc_pure
+ Error LockGetError() const {
+ Lock();
+ Error result = GetError();
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Clear the error condition and free the #Error object (if any).
+ *
+ * Caller must lock the object.
+ */
+ void ClearError() {
+ if (state == DecoderState::ERROR) {
+ error.Clear();
+ state = DecoderState::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 DetachedSong &_song) const;
+
+ gcc_pure
+ bool LockIsCurrentSong(const DetachedSong &_song) const {
+ Lock();
+ const bool result = IsCurrentSong(_song);
+ Unlock();
+ return result;
+ }
+
+private:
+ /**
+ * Wait for the command to be finished by the decoder thread.
+ *
+ * To be called from the client thread. Caller must lock the
+ * object.
+ */
+ void WaitCommandLocked() {
+ while (command != DecoderCommand::NONE)
+ WaitForDecoder();
+ }
+
+ /**
+ * Send a command to the decoder thread and synchronously wait
+ * for it to finish.
+ *
+ * To be called from the client thread. Caller must lock the
+ * object.
+ */
+ void SynchronousCommandLocked(DecoderCommand cmd) {
+ command = cmd;
+ Signal();
+ WaitCommandLocked();
+ }
+
+ /**
+ * Send a command to the decoder thread and synchronously wait
+ * for it to finish.
+ *
+ * To be called from the client thread. This method locks the
+ * object.
+ */
+ void LockSynchronousCommand(DecoderCommand cmd) {
+ Lock();
+ ClearError();
+ SynchronousCommandLocked(cmd);
+ Unlock();
+ }
+
+ void LockAsynchronousCommand(DecoderCommand cmd) {
+ Lock();
+ command = cmd;
+ Signal();
+ Unlock();
+ }
+
+public:
+ /**
+ * Start the decoder.
+ *
+ * @param song the song to be decoded; the given instance will be
+ * owned and freed by the decoder
+ * @param start_time see #DecoderControl
+ * @param end_time see #DecoderControl
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
+ void Start(DetachedSong *song, SongTime start_time, SongTime end_time,
+ MusicBuffer &buffer, MusicPipe &pipe);
+
+ void Stop();
+
+ bool Seek(SongTime t);
+
+ void Quit();
+
+ const char *GetMixRampStart() const {
+ return mix_ramp.GetStart();
+ }
+
+ const char *GetMixRampEnd() const {
+ return mix_ramp.GetEnd();
+ }
+
+ const char *GetMixRampPreviousEnd() const {
+ return previous_mix_ramp.GetEnd();
+ }
+
+ void SetMixRamp(MixRampInfo &&new_value) {
+ mix_ramp = std::move(new_value);
+ }
+
+ /**
+ * Move mixramp_end to mixramp_prev_end and clear
+ * mixramp_start/mixramp_end.
+ */
+ void CycleMixRamp();
+};
+
+#endif
diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx
new file mode 100644
index 000000000..bd3842837
--- /dev/null
+++ b/src/decoder/DecoderError.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "DecoderError.hxx"
+#include "util/Domain.hxx"
+
+const Domain decoder_domain("decoder");
diff --git a/src/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx
new file mode 100644
index 000000000..83cf98204
--- /dev/null
+++ b/src/decoder/DecoderError.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_ERROR_HXX
+#define MPD_DECODER_ERROR_HXX
+
+extern const class Domain decoder_domain;
+
+#endif
diff --git a/src/decoder/DecoderInternal.cxx b/src/decoder/DecoderInternal.cxx
new file mode 100644
index 000000000..f35878682
--- /dev/null
+++ b/src/decoder/DecoderInternal.cxx
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderInternal.hxx"
+#include "DecoderControl.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "tag/Tag.hxx"
+
+#include <assert.h>
+
+Decoder::~Decoder()
+{
+ /* caller must flush the chunk */
+ assert(chunk == nullptr);
+
+ if (convert != nullptr) {
+ convert->Close();
+ delete convert;
+ }
+
+ delete song_tag;
+ delete stream_tag;
+ delete decoder_tag;
+}
+
+/**
+ * All chunks are full of decoded data; wait for the player to free
+ * one.
+ */
+static DecoderCommand
+need_chunks(DecoderControl &dc)
+{
+ if (dc.command == DecoderCommand::NONE)
+ dc.Wait();
+
+ return dc.command;
+}
+
+MusicChunk *
+Decoder::GetChunk()
+{
+ DecoderCommand cmd;
+
+ if (chunk != nullptr)
+ return chunk;
+
+ do {
+ chunk = dc.buffer->Allocate();
+ if (chunk != nullptr) {
+ chunk->replay_gain_serial = replay_gain_serial;
+ if (replay_gain_serial != 0)
+ chunk->replay_gain_info = replay_gain_info;
+
+ return chunk;
+ }
+
+ dc.Lock();
+ cmd = need_chunks(dc);
+ dc.Unlock();
+ } while (cmd == DecoderCommand::NONE);
+
+ return nullptr;
+}
+
+void
+Decoder::FlushChunk()
+{
+ assert(!seeking);
+ assert(!initial_seek_running);
+ assert(!initial_seek_pending);
+ assert(chunk != nullptr);
+
+ if (chunk->IsEmpty())
+ dc.buffer->Return(chunk);
+ else
+ dc.pipe->Push(chunk);
+
+ chunk = nullptr;
+
+ dc.Lock();
+ if (dc.client_is_waiting)
+ dc.client_cond.signal();
+ dc.Unlock();
+}
diff --git a/src/decoder/DecoderInternal.hxx b/src/decoder/DecoderInternal.hxx
new file mode 100644
index 000000000..24b665e85
--- /dev/null
+++ b/src/decoder/DecoderInternal.hxx
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_INTERNAL_HXX
+#define MPD_DECODER_INTERNAL_HXX
+
+#include "ReplayGainInfo.hxx"
+#include "util/Error.hxx"
+
+class PcmConvert;
+struct MusicChunk;
+struct DecoderControl;
+struct Tag;
+
+struct Decoder {
+ DecoderControl &dc;
+
+ /**
+ * For converting input data to the configured audio format.
+ * nullptr means no conversion necessary.
+ */
+ PcmConvert *convert;
+
+ /**
+ * The time stamp of the next data chunk, in seconds.
+ */
+ 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_time(), 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 */
+ MusicChunk *chunk;
+
+ ReplayGainInfo replay_gain_info;
+
+ /**
+ * A positive serial number for checking if replay gain info
+ * has changed since the last check.
+ */
+ unsigned replay_gain_serial;
+
+ /**
+ * An error has occurred (in DecoderAPI.cxx), and the plugin
+ * will be asked to stop.
+ */
+ Error error;
+
+ Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag)
+ :dc(_dc),
+ convert(nullptr),
+ timestamp(0),
+ initial_seek_pending(_initial_seek_pending),
+ initial_seek_running(false),
+ 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
+ */
+ MusicChunk *GetChunk();
+
+ /**
+ * Flushes the current chunk.
+ *
+ * Caller must not lock the #DecoderControl object.
+ */
+ void FlushChunk();
+};
+
+#endif
diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx
new file mode 100644
index 000000000..cd6881ce2
--- /dev/null
+++ b/src/decoder/DecoderList.cxx
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "plugins/AudiofileDecoderPlugin.hxx"
+#include "plugins/PcmDecoderPlugin.hxx"
+#include "plugins/DsdiffDecoderPlugin.hxx"
+#include "plugins/DsfDecoderPlugin.hxx"
+#include "plugins/FlacDecoderPlugin.h"
+#include "plugins/OpusDecoderPlugin.h"
+#include "plugins/VorbisDecoderPlugin.h"
+#include "plugins/AdPlugDecoderPlugin.h"
+#include "plugins/WavpackDecoderPlugin.hxx"
+#include "plugins/FfmpegDecoderPlugin.hxx"
+#include "plugins/GmeDecoderPlugin.hxx"
+#include "plugins/FaadDecoderPlugin.hxx"
+#include "plugins/MadDecoderPlugin.hxx"
+#include "plugins/SndfileDecoderPlugin.hxx"
+#include "plugins/Mpg123DecoderPlugin.hxx"
+#include "plugins/WildmidiDecoderPlugin.hxx"
+#include "plugins/MikmodDecoderPlugin.hxx"
+#include "plugins/ModplugDecoderPlugin.hxx"
+#include "plugins/MpcdecDecoderPlugin.hxx"
+#include "plugins/FluidsynthDecoderPlugin.hxx"
+#include "plugins/SidplayDecoderPlugin.hxx"
+#include "util/Macros.hxx"
+
+#include <string.h>
+
+const struct DecoderPlugin *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
+#ifdef ENABLE_DSD
+ &dsdiff_decoder_plugin,
+ &dsf_decoder_plugin,
+#endif
+#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,
+ nullptr
+};
+
+static constexpr unsigned num_decoder_plugins =
+ ARRAY_SIZE(decoder_plugins) - 1;
+
+/** which plugins have been initialized successfully? */
+bool decoder_plugins_enabled[num_decoder_plugins];
+
+const struct DecoderPlugin *
+decoder_plugin_from_name(const char *name)
+{
+ return decoder_plugins_find([=](const DecoderPlugin &plugin){
+ return strcmp(plugin.name, name) == 0;
+ });
+}
+
+void decoder_plugin_init_all(void)
+{
+ struct config_param empty;
+
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) {
+ const DecoderPlugin &plugin = *decoder_plugins[i];
+ const struct config_param *param =
+ config_find_block(CONF_DECODER, "plugin", plugin.name);
+
+ if (param == nullptr)
+ param = &empty;
+ else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (plugin.Init(*param))
+ decoder_plugins_enabled[i] = true;
+ }
+}
+
+void decoder_plugin_deinit_all(void)
+{
+ decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
+ plugin.Finish();
+ });
+}
+
+bool
+decoder_plugins_supports_suffix(const char *suffix)
+{
+ return decoder_plugins_try([suffix](const DecoderPlugin &plugin){
+ return plugin.SupportsSuffix(suffix);
+ });
+}
diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx
new file mode 100644
index 000000000..47085d4ae
--- /dev/null
+++ b/src/decoder/DecoderList.hxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_LIST_HXX
+#define MPD_DECODER_LIST_HXX
+
+#include "Compiler.h"
+
+struct DecoderPlugin;
+
+extern const struct DecoderPlugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
+/* interface for using plugins */
+
+gcc_pure
+const struct DecoderPlugin *
+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);
+
+template<typename F>
+static inline const DecoderPlugin *
+decoder_plugins_find(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
+ return decoder_plugins[i];
+
+ return nullptr;
+}
+
+template<typename F>
+static inline bool
+decoder_plugins_try(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
+ return true;
+
+ return false;
+}
+
+template<typename F>
+static inline void
+decoder_plugins_for_each(F f)
+{
+ for (auto i = decoder_plugins; *i != nullptr; ++i)
+ f(**i);
+}
+
+template<typename F>
+static inline void
+decoder_plugins_for_each_enabled(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i])
+ f(*decoder_plugins[i]);
+}
+
+/**
+ * Is there at least once #DecoderPlugin that supports the specified
+ * file name suffix?
+ */
+gcc_pure gcc_nonnull_all
+bool
+decoder_plugins_supports_suffix(const char *suffix);
+
+#endif
diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx
new file mode 100644
index 000000000..a0722c348
--- /dev/null
+++ b/src/decoder/DecoderPlugin.cxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderPlugin.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+bool
+DecoderPlugin::SupportsSuffix(const char *suffix) const
+{
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
+ assert(suffix != nullptr);
+#endif
+
+ return suffixes != nullptr && string_array_contains(suffixes, suffix);
+
+}
+
+bool
+DecoderPlugin::SupportsMimeType(const char *mime_type) const
+{
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
+ assert(mime_type != nullptr);
+#endif
+
+ return mime_types != nullptr &&
+ string_array_contains(mime_types, mime_type);
+}
diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx
new file mode 100644
index 000000000..dbf3db9aa
--- /dev/null
+++ b/src/decoder/DecoderPlugin.hxx
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_PLUGIN_HXX
+#define MPD_DECODER_PLUGIN_HXX
+
+#include "Compiler.h"
+
+struct config_param;
+class InputStream;
+struct tag_handler;
+class Path;
+
+/**
+ * Opaque handle which the decoder plugin passes to the functions in
+ * this header.
+ */
+struct Decoder;
+
+struct DecoderPlugin {
+ 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)(Decoder &decoder, InputStream &is);
+
+ /**
+ * Decode a local file.
+ *
+ * Either implement this method or stream_decode().
+ */
+ void (*file_decode)(Decoder &decoder, Path path_fs);
+
+ /**
+ * Scan metadata of a file.
+ *
+ * @return false if the operation has failed
+ */
+ bool (*scan_file)(Path 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)(InputStream &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
+ *
+ * Free the return value with delete[].
+ */
+ char* (*container_scan)(Path 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
+ */
+ bool Init(const config_param &param) const {
+ return init != nullptr
+ ? init(param)
+ : true;
+ }
+
+ /**
+ * Deinitialize a decoder plugin which was initialized successfully.
+ */
+ void Finish() const {
+ if (finish != nullptr)
+ finish();
+ }
+
+ /**
+ * Decode a stream.
+ */
+ void StreamDecode(Decoder &decoder, InputStream &is) const {
+ stream_decode(decoder, is);
+ }
+
+ /**
+ * Decode a file.
+ */
+ template<typename P>
+ void FileDecode(Decoder &decoder, P path_fs) const {
+ file_decode(decoder, path_fs);
+ }
+
+ /**
+ * Read the tag of a file.
+ */
+ template<typename P>
+ bool ScanFile(P path_fs,
+ const tag_handler &handler, void *handler_ctx) const {
+ return scan_file != nullptr
+ ? scan_file(path_fs, &handler, handler_ctx)
+ : false;
+ }
+
+ /**
+ * Read the tag of a stream.
+ */
+ bool ScanStream(InputStream &is,
+ const tag_handler &handler, void *handler_ctx) const {
+ return scan_stream != nullptr
+ ? scan_stream(is, &handler, handler_ctx)
+ : false;
+ }
+
+ /**
+ * return "virtual" tracks in a container
+ */
+ template<typename P>
+ char *ContainerScan(P path, const unsigned int tnum) const {
+ return container_scan(path, tnum);
+ }
+
+ /**
+ * Does the plugin announce the specified file name suffix?
+ */
+ gcc_pure gcc_nonnull_all
+ bool SupportsSuffix(const char *suffix) const;
+
+ /**
+ * Does the plugin announce the specified MIME type?
+ */
+ gcc_pure gcc_nonnull_all
+ bool SupportsMimeType(const char *mime_type) const;
+};
+
+#endif
diff --git a/src/decoder/DecoderPrint.cxx b/src/decoder/DecoderPrint.cxx
new file mode 100644
index 000000000..54b89c36c
--- /dev/null
+++ b/src/decoder/DecoderPrint.cxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderPrint.hxx"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "client/Client.hxx"
+
+#include <functional>
+
+#include <assert.h>
+
+static void
+decoder_plugin_print(Client &client,
+ const DecoderPlugin &plugin)
+{
+ const char *const*p;
+
+ assert(plugin.name != nullptr);
+
+ client_printf(client, "plugin: %s\n", plugin.name);
+
+ if (plugin.suffixes != nullptr)
+ for (p = plugin.suffixes; *p != nullptr; ++p)
+ client_printf(client, "suffix: %s\n", *p);
+
+ if (plugin.mime_types != nullptr)
+ for (p = plugin.mime_types; *p != nullptr; ++p)
+ client_printf(client, "mime_type: %s\n", *p);
+}
+
+void
+decoder_list_print(Client &client)
+{
+ using namespace std::placeholders;
+ const auto f = std::bind(decoder_plugin_print, std::ref(client), _1);
+ decoder_plugins_for_each_enabled(f);
+}
diff --git a/src/decoder/DecoderPrint.hxx b/src/decoder/DecoderPrint.hxx
new file mode 100644
index 000000000..695bd099d
--- /dev/null
+++ b/src/decoder/DecoderPrint.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_PRINT_HXX
+#define MPD_DECODER_PRINT_HXX
+
+class Client;
+
+void
+decoder_list_print(Client &client);
+
+#endif
diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx
new file mode 100644
index 000000000..dd5518b98
--- /dev/null
+++ b/src/decoder/DecoderThread.cxx
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderThread.hxx"
+#include "DecoderControl.hxx"
+#include "DecoderInternal.hxx"
+#include "DecoderError.hxx"
+#include "DecoderPlugin.hxx"
+#include "DetachedSong.hxx"
+#include "system/FatalError.hxx"
+#include "MusicPipe.hxx"
+#include "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "input/LocalOpen.hxx"
+#include "DecoderList.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "thread/Name.hxx"
+#include "tag/ApeReplayGain.hxx"
+#include "Log.hxx"
+
+#include <functional>
+
+static constexpr Domain decoder_thread_domain("decoder_thread");
+
+/**
+ * Marks the current decoder command as "finished" and notifies the
+ * player thread.
+ *
+ * @param dc the #DecoderControl object; must be locked
+ */
+static void
+decoder_command_finished_locked(DecoderControl &dc)
+{
+ assert(dc.command != DecoderCommand::NONE);
+
+ dc.command = DecoderCommand::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 #DecoderCommand::STOP is
+ * received, nullptr on error
+ */
+static InputStream *
+decoder_input_stream_open(DecoderControl &dc, const char *uri)
+{
+ Error error;
+
+ InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error);
+ if (is == nullptr) {
+ if (error.IsDefined())
+ LogError(error);
+
+ return nullptr;
+ }
+
+ /* wait for the input stream to become ready; its metadata
+ will be available then */
+
+ dc.Lock();
+
+ is->Update();
+ while (!is->IsReady() &&
+ dc.command != DecoderCommand::STOP) {
+ dc.Wait();
+
+ is->Update();
+ }
+
+ if (!is->Check(error)) {
+ dc.Unlock();
+
+ LogError(error);
+ return nullptr;
+ }
+
+ dc.Unlock();
+
+ return is;
+}
+
+static InputStream *
+decoder_input_stream_open(DecoderControl &dc, Path path)
+{
+ Error error;
+
+ InputStream *is = OpenLocalInputStream(path, dc.mutex, dc.cond, error);
+ if (is == nullptr) {
+ LogError(error);
+ return nullptr;
+ }
+
+ assert(is->IsReady());
+
+ return is;
+}
+
+static bool
+decoder_stream_decode(const DecoderPlugin &plugin,
+ Decoder &decoder,
+ InputStream &input_stream)
+{
+ assert(plugin.stream_decode != nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ assert(input_stream.IsReady());
+ assert(decoder.dc.state == DecoderState::START);
+
+ FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
+
+ if (decoder.dc.command == DecoderCommand::STOP)
+ return true;
+
+ /* rewind the stream, so each plugin gets a fresh start */
+ input_stream.Rewind(IgnoreError());
+
+ decoder.dc.Unlock();
+
+ FormatThreadName("decoder:%s", plugin.name);
+
+ plugin.StreamDecode(decoder, input_stream);
+
+ SetThreadName("decoder");
+
+ decoder.dc.Lock();
+
+ assert(decoder.dc.state == DecoderState::START ||
+ decoder.dc.state == DecoderState::DECODE);
+
+ return decoder.dc.state != DecoderState::START;
+}
+
+static bool
+decoder_file_decode(const DecoderPlugin &plugin,
+ Decoder &decoder, Path path)
+{
+ assert(plugin.file_decode != nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ assert(!path.IsNull());
+ assert(path.IsAbsolute());
+ assert(decoder.dc.state == DecoderState::START);
+
+ FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
+
+ if (decoder.dc.command == DecoderCommand::STOP)
+ return true;
+
+ decoder.dc.Unlock();
+
+ FormatThreadName("decoder:%s", plugin.name);
+
+ plugin.FileDecode(decoder, path);
+
+ SetThreadName("decoder");
+
+ decoder.dc.Lock();
+
+ assert(decoder.dc.state == DecoderState::START ||
+ decoder.dc.state == DecoderState::DECODE);
+
+ return decoder.dc.state != DecoderState::START;
+}
+
+gcc_pure
+static bool
+decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is)
+{
+ assert(plugin.stream_decode != nullptr);
+
+ const char *mime_type = is.GetMimeType();
+ return mime_type != nullptr && plugin.SupportsMimeType(mime_type);
+}
+
+gcc_pure
+static bool
+decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix)
+{
+ assert(plugin.stream_decode != nullptr);
+
+ return suffix != nullptr && plugin.SupportsSuffix(suffix);
+}
+
+gcc_pure
+static bool
+decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is,
+ const char *suffix)
+{
+ return plugin.stream_decode != nullptr &&
+ (decoder_check_plugin_mime(plugin, is) ||
+ decoder_check_plugin_suffix(plugin, suffix));
+}
+
+static bool
+decoder_run_stream_plugin(Decoder &decoder, InputStream &is,
+ const char *suffix,
+ const DecoderPlugin &plugin,
+ bool &tried_r)
+{
+ if (!decoder_check_plugin(plugin, is, suffix))
+ return false;
+
+ tried_r = true;
+ return decoder_stream_decode(plugin, decoder, is);
+}
+
+static bool
+decoder_run_stream_locked(Decoder &decoder, InputStream &is,
+ const char *uri, bool &tried_r)
+{
+ UriSuffixBuffer suffix_buffer;
+ const char *const suffix = uri_get_suffix(uri, suffix_buffer);
+
+ using namespace std::placeholders;
+ const auto f = std::bind(decoder_run_stream_plugin,
+ std::ref(decoder), std::ref(is), suffix,
+ _1, std::ref(tried_r));
+ return decoder_plugins_try(f);
+}
+
+/**
+ * Try decoding a stream, using the fallback plugin.
+ */
+static bool
+decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
+{
+ const struct DecoderPlugin *plugin;
+
+ plugin = decoder_plugin_from_name("mad");
+ return plugin != nullptr && plugin->stream_decode != nullptr &&
+ decoder_stream_decode(*plugin, decoder, is);
+}
+
+/**
+ * Try decoding a stream.
+ */
+static bool
+decoder_run_stream(Decoder &decoder, const char *uri)
+{
+ DecoderControl &dc = decoder.dc;
+ InputStream *input_stream;
+ bool success;
+
+ dc.Unlock();
+
+ input_stream = decoder_input_stream_open(dc, uri);
+ if (input_stream == nullptr) {
+ dc.Lock();
+ return false;
+ }
+
+ dc.Lock();
+
+ bool tried = false;
+ success = dc.command == DecoderCommand::STOP ||
+ decoder_run_stream_locked(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 &&
+ decoder_run_stream_fallback(decoder, *input_stream));
+
+ dc.Unlock();
+ delete 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(Decoder &decoder, Path path_fs)
+{
+ ReplayGainInfo info;
+ if (replay_gain_ape_read(path_fs, info))
+ decoder_replay_gain(decoder, &info);
+}
+
+static bool
+TryDecoderFile(Decoder &decoder, Path path_fs, const char *suffix,
+ const DecoderPlugin &plugin)
+{
+ if (!plugin.SupportsSuffix(suffix))
+ return false;
+
+ DecoderControl &dc = decoder.dc;
+
+ if (plugin.file_decode != nullptr) {
+ dc.Lock();
+
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
+
+ dc.Unlock();
+ } else if (plugin.stream_decode != nullptr) {
+ InputStream *input_stream =
+ decoder_input_stream_open(dc, path_fs);
+ if (input_stream == nullptr)
+ return false;
+
+ dc.Lock();
+
+ bool success = decoder_stream_decode(plugin, decoder,
+ *input_stream);
+
+ dc.Unlock();
+
+ delete input_stream;
+
+ if (success) {
+ dc.Lock();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(Decoder &decoder, const char *uri_utf8, Path path_fs)
+{
+ const char *suffix = uri_get_suffix(uri_utf8);
+ if (suffix == nullptr)
+ return false;
+
+ DecoderControl &dc = decoder.dc;
+ dc.Unlock();
+
+ decoder_load_replay_gain(decoder, path_fs);
+
+ if (decoder_plugins_try([&decoder, path_fs,
+ suffix](const DecoderPlugin &plugin){
+ return TryDecoderFile(decoder,
+ path_fs, suffix,
+ plugin);
+ }))
+ return true;
+
+ dc.Lock();
+ return false;
+}
+
+static void
+decoder_run_song(DecoderControl &dc,
+ const DetachedSong &song, const char *uri, Path path_fs)
+{
+ Decoder decoder(dc, dc.start_time.IsPositive(),
+ new Tag(song.GetTag()));
+ int ret;
+
+ dc.state = DecoderState::START;
+
+ decoder_command_finished_locked(dc);
+
+ ret = !path_fs.IsNull()
+ ? decoder_run_file(decoder, uri, path_fs)
+ : decoder_run_stream(decoder, uri);
+
+ dc.Unlock();
+
+ /* flush the last chunk */
+
+ if (decoder.chunk != nullptr)
+ decoder.FlushChunk();
+
+ dc.Lock();
+
+ if (decoder.error.IsDefined()) {
+ /* copy the Error from sruct Decoder to
+ DecoderControl */
+ dc.state = DecoderState::ERROR;
+ dc.error = std::move(decoder.error);
+ } else if (ret)
+ dc.state = DecoderState::STOP;
+ else {
+ dc.state = DecoderState::ERROR;
+
+ const char *error_uri = song.GetURI();
+ const std::string allocated = uri_remove_auth(error_uri);
+ if (!allocated.empty())
+ error_uri = allocated.c_str();
+
+ dc.error.Format(decoder_domain,
+ "Failed to decode %s", error_uri);
+ }
+
+ dc.client_cond.signal();
+}
+
+static void
+decoder_run(DecoderControl &dc)
+{
+ dc.ClearError();
+
+ assert(dc.song != nullptr);
+ const DetachedSong &song = *dc.song;
+
+ const char *const uri_utf8 = song.GetRealURI();
+
+ Path path_fs = Path::Null();
+ AllocatedPath path_buffer = AllocatedPath::Null();
+ if (PathTraitsUTF8::IsAbsolute(uri_utf8)) {
+ path_buffer = AllocatedPath::FromUTF8(uri_utf8, dc.error);
+ if (path_buffer.IsNull()) {
+ dc.state = DecoderState::ERROR;
+ decoder_command_finished_locked(dc);
+ return;
+ }
+
+ path_fs = path_buffer;
+ }
+
+ decoder_run_song(dc, song, uri_utf8, path_fs);
+
+}
+
+static void
+decoder_task(void *arg)
+{
+ DecoderControl &dc = *(DecoderControl *)arg;
+
+ SetThreadName("decoder");
+
+ dc.Lock();
+
+ do {
+ assert(dc.state == DecoderState::STOP ||
+ dc.state == DecoderState::ERROR);
+
+ switch (dc.command) {
+ case DecoderCommand::START:
+ dc.CycleMixRamp();
+ dc.replay_gain_prev_db = dc.replay_gain_db;
+ dc.replay_gain_db = 0;
+
+ decoder_run(dc);
+ break;
+
+ case DecoderCommand::SEEK:
+ /* this seek was too late, and the decoder had
+ already finished; start a new decoder */
+
+ /* we need to clear the pipe here; usually the
+ PlayerThread is responsible, but it is not
+ aware that the decoder has finished */
+ dc.pipe->Clear(*dc.buffer);
+
+ decoder_run(dc);
+ break;
+
+ case DecoderCommand::STOP:
+ decoder_command_finished_locked(dc);
+ break;
+
+ case DecoderCommand::NONE:
+ dc.Wait();
+ break;
+ }
+ } while (dc.command != DecoderCommand::NONE || !dc.quit);
+
+ dc.Unlock();
+}
+
+void
+decoder_thread_start(DecoderControl &dc)
+{
+ assert(!dc.thread.IsDefined());
+
+ dc.quit = false;
+
+ Error error;
+ if (!dc.thread.Start(decoder_task, &dc, error))
+ FatalError(error);
+}
diff --git a/src/decoder/DecoderThread.hxx b/src/decoder/DecoderThread.hxx
new file mode 100644
index 000000000..d5fde281c
--- /dev/null
+++ b/src/decoder/DecoderThread.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_THREAD_HXX
+#define MPD_DECODER_THREAD_HXX
+
+struct DecoderControl;
+
+void
+decoder_thread_start(DecoderControl &dc);
+
+#endif
diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx
deleted file mode 100644
index eafedda8f..000000000
--- a/src/decoder/DsdLib.cxx
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/* \file
- *
- * This file contains functions used by the DSF and DSDIFF decoders.
- *
- */
-
-#include "config.h"
-#include "DsdLib.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "util/bit_reverse.h"
-#include "tag/TagHandler.hxx"
-#include "tag/TagId3.hxx"
-#include "util/Error.hxx"
-
-#include <unistd.h>
-#include <string.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
-#ifdef HAVE_ID3TAG
-#include <id3tag.h>
-#endif
-
-bool
-DsdId::Equals(const char *s) const
-{
- assert(s != nullptr);
- assert(strlen(s) == sizeof(value));
-
- return memcmp(value, s, sizeof(value)) == 0;
-}
-
-/**
- * Skip the #input_stream to the specified offset.
- */
-bool
-dsdlib_skip_to(Decoder *decoder, InputStream &is,
- int64_t offset)
-{
- if (is.IsSeekable())
- return is.Seek(offset, SEEK_SET, IgnoreError());
-
- if (is.GetOffset() > offset)
- return false;
-
- char buffer[8192];
- while (is.GetOffset() < offset) {
- size_t length = sizeof(buffer);
- if (offset - is.GetOffset() < (int64_t)length)
- length = offset - is.GetOffset();
-
- size_t nbytes = decoder_read(decoder, is, buffer, length);
- if (nbytes == 0)
- return false;
- }
-
- assert(is.GetOffset() == offset);
- return true;
-}
-
-/**
- * Skip some bytes from the #input_stream.
- */
-bool
-dsdlib_skip(Decoder *decoder, InputStream &is,
- int64_t delta)
-{
- assert(delta >= 0);
-
- if (delta == 0)
- return true;
-
- if (is.IsSeekable())
- return is.Seek(delta, SEEK_CUR, IgnoreError());
-
- char buffer[8192];
- while (delta > 0) {
- size_t length = sizeof(buffer);
- if ((int64_t)length > delta)
- length = delta;
-
- size_t nbytes = decoder_read(decoder, is, buffer, length);
- if (nbytes == 0)
- return false;
-
- delta -= nbytes;
- }
-
- return true;
-}
-
-#ifdef HAVE_ID3TAG
-void
-dsdlib_tag_id3(InputStream &is,
- const struct tag_handler *handler,
- void *handler_ctx, int64_t 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 auto size = is.GetSize();
- const auto offset = is.GetOffset();
- 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 (!decoder_read_full(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
deleted file mode 100644
index 5c6127149..000000000
--- a/src/decoder/DsdLib.hxx
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_DSDLIB_HXX
-#define MPD_DECODER_DSDLIB_HXX
-
-#include "system/ByteOrder.hxx"
-#include "Compiler.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-struct Decoder;
-struct InputStream;
-
-struct DsdId {
- char value[4];
-
- gcc_pure
- bool Equals(const char *s) const;
-};
-
-class DsdUint64 {
- uint32_t lo;
- uint32_t hi;
-
-public:
- constexpr uint64_t Read() const {
- return (uint64_t(FromLE32(hi)) << 32) |
- uint64_t(FromLE32(lo));
- }
-};
-
-class DffDsdUint64 {
- uint32_t hi;
- uint32_t lo;
-
-public:
- constexpr uint64_t Read() const {
- return (uint64_t(FromBE32(hi)) << 32) |
- uint64_t(FromBE32(lo));
- }
-};
-
-bool
-dsdlib_skip_to(Decoder *decoder, InputStream &is,
- int64_t offset);
-
-bool
-dsdlib_skip(Decoder *decoder, InputStream &is,
- int64_t delta);
-
-/**
- * Add tags from ID3 tag. All tags commonly found in the ID3 tags of
- * DSF and DSDIFF files are imported
- */
-void
-dsdlib_tag_id3(InputStream &is,
- const struct tag_handler *handler,
- void *handler_ctx, int64_t tagoffset);
-
-#endif
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx
deleted file mode 100644
index 767395215..000000000
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/* \file
- *
- * 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 "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/bit_reverse.h"
-#include "util/Error.hxx"
-#include "system/ByteOrder.hxx"
-#include "tag/TagHandler.hxx"
-#include "DsdLib.hxx"
-#include "Log.hxx"
-
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
-struct DsdiffHeader {
- DsdId id;
- DffDsdUint64 size;
- DsdId format;
-};
-
-struct DsdiffChunkHeader {
- DsdId id;
- DffDsdUint64 size;
-
- /**
- * Read the "size" attribute from the specified header, converting it
- * to the host byte order if needed.
- */
- constexpr
- uint64_t GetSize() const {
- return size.Read();
- }
-};
-
-/** 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
- InputStream::offset_type id3_offset;
- uint64_t id3_size;
-#endif
- /** offset for artist tag */
- InputStream::offset_type diar_offset;
- /** offset for title tag */
- InputStream::offset_type 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(Decoder *decoder, InputStream &is,
- DsdId *id)
-{
- return decoder_read_full(decoder, is, id, sizeof(*id));
-}
-
-static bool
-dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
- DsdiffChunkHeader *header)
-{
- return decoder_read_full(decoder, is, header, sizeof(*header));
-}
-
-static bool
-dsdiff_read_payload(Decoder *decoder, InputStream &is,
- const DsdiffChunkHeader *header,
- void *data, size_t length)
-{
- uint64_t size = header->GetSize();
- if (size != (uint64_t)length)
- return false;
-
- return decoder_read_full(decoder, is, data, length);
-}
-
-/**
- * Read and parse a "SND" chunk inside "PROP".
- */
-static bool
-dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
- DsdiffMetaData *metadata,
- InputStream::offset_type end_offset)
-{
- DsdiffChunkHeader header;
- while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) {
- if (!dsdiff_read_chunk_header(decoder, is, &header))
- return false;
-
- InputStream::offset_type chunk_end_offset = is.GetOffset()
- + header.GetSize();
- if (chunk_end_offset > end_offset)
- return false;
-
- if (header.id.Equals("FS ")) {
- uint32_t sample_rate;
- if (!dsdiff_read_payload(decoder, is, &header,
- &sample_rate,
- sizeof(sample_rate)))
- return false;
-
- metadata->sample_rate = FromBE32(sample_rate);
- } else if (header.id.Equals("CHNL")) {
- uint16_t channels;
- if (header.GetSize() < sizeof(channels) ||
- !decoder_read_full(decoder, is,
- &channels, sizeof(channels)) ||
- !dsdlib_skip_to(decoder, is, chunk_end_offset))
- return false;
-
- metadata->channels = FromBE16(channels);
- } else if (header.id.Equals("CMPR")) {
- DsdId type;
- if (header.GetSize() < sizeof(type) ||
- !decoder_read_full(decoder, is,
- &type, sizeof(type)) ||
- !dsdlib_skip_to(decoder, is, chunk_end_offset))
- return false;
-
- if (!type.Equals("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.GetOffset() == end_offset;
-}
-
-/**
- * Read and parse a "PROP" chunk.
- */
-static bool
-dsdiff_read_prop(Decoder *decoder, InputStream &is,
- DsdiffMetaData *metadata,
- const DsdiffChunkHeader *prop_header)
-{
- uint64_t prop_size = prop_header->GetSize();
- InputStream::offset_type end_offset = is.GetOffset() + prop_size;
-
- DsdId prop_id;
- if (prop_size < sizeof(prop_id) ||
- !dsdiff_read_id(decoder, is, &prop_id))
- return false;
-
- if (prop_id.Equals("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(InputStream &is,
- const struct tag_handler *handler,
- void *handler_ctx, InputStream::offset_type tagoffset,
- TagType type)
-{
- if (!dsdlib_skip_to(nullptr, is, tagoffset))
- return;
-
- struct dsdiff_native_tag metatag;
-
- if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
- return;
-
- uint32_t length = FromBE32(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 (!decoder_read_full(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(Decoder *decoder, InputStream &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;
-
- metadata->diar_offset = 0;
- metadata->diti_offset = 0;
-
-#ifdef HAVE_ID3TAG
- metadata->id3_offset = 0;
-#endif
-
- /* Now process all the remaining chunk headers in the stream
- and record their position and size */
-
- do {
- uint64_t chunk_size = chunk_header->GetSize();
-
- /* DIIN chunk, is directly followed by other chunks */
- if (chunk_header->id.Equals("DIIN"))
- chunk_size = 0;
-
- /* DIAR chunk - DSDIFF native tag for Artist */
- if (chunk_header->id.Equals("DIAR")) {
- chunk_size = chunk_header->GetSize();
- metadata->diar_offset = is.GetOffset();
- }
-
- /* DITI chunk - DSDIFF native tag for Title */
- if (chunk_header->id.Equals("DITI")) {
- chunk_size = chunk_header->GetSize();
- metadata->diti_offset = is.GetOffset();
- }
-#ifdef HAVE_ID3TAG
- /* 'ID3 ' chunk, offspec. Used by sacdextract */
- if (chunk_header->id.Equals("ID3 ")) {
- chunk_size = chunk_header->GetSize();
- metadata->id3_offset = is.GetOffset();
- metadata->id3_size = chunk_size;
- }
-#endif
-
- if (!dsdlib_skip(decoder, is, chunk_size))
- break;
- } while (dsdiff_read_chunk_header(decoder, is, chunk_header));
-
- /* 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(Decoder *decoder, InputStream &is,
- DsdiffMetaData *metadata,
- DsdiffChunkHeader *chunk_header)
-{
- DsdiffHeader header;
- if (!decoder_read_full(decoder, is, &header, sizeof(header)) ||
- !header.id.Equals("FRM8") ||
- !header.format.Equals("DSD "))
- return false;
-
- while (true) {
- if (!dsdiff_read_chunk_header(decoder, is,
- chunk_header))
- return false;
-
- if (chunk_header->id.Equals("PROP")) {
- if (!dsdiff_read_prop(decoder, is, metadata,
- chunk_header))
- return false;
- } else if (chunk_header->id.Equals("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();
- InputStream::offset_type chunk_end_offset =
- is.GetOffset() + 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(Decoder &decoder, InputStream &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 >= frame_size) {
- /* see how much aligned data from the remaining chunk
- fits into the local buffer */
- size_t now_size = buffer_size;
- if (chunk_size < (uint64_t)now_size) {
- unsigned now_frames =
- (unsigned)chunk_size / frame_size;
- now_size = now_frames * frame_size;
- }
-
- if (!decoder_read_full(&decoder, is, buffer, now_size))
- return false;
-
- const size_t nbytes = now_size;
- chunk_size -= nbytes;
-
- if (lsbitfirst)
- bit_reverse_buffer(buffer, buffer + nbytes);
-
- const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0);
- switch (cmd) {
- case DecoderCommand::NONE:
- break;
-
- case DecoderCommand::START:
- case DecoderCommand::STOP:
- return false;
-
- case DecoderCommand::SEEK:
-
- /* Not implemented yet */
- decoder_seek_error(decoder);
- break;
- }
- }
- return dsdlib_skip(&decoder, is, chunk_size);
-}
-
-static void
-dsdiff_stream_decode(Decoder &decoder, InputStream &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;
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
- SampleFormat::DSD,
- metadata.channels, error)) {
- LogError(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 (chunk_header.id.Equals("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(InputStream &is,
- gcc_unused const struct tag_handler *handler,
- gcc_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, IgnoreError()))
- /* 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 DecoderPlugin 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
deleted file mode 100644
index be14fc9cd..000000000
--- a/src/decoder/DsdiffDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_DSDIFF_H
-#define MPD_DECODER_DSDIFF_H
-
-extern const struct DecoderPlugin dsdiff_decoder_plugin;
-
-#endif
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx
deleted file mode 100644
index 9fbfe9cda..000000000
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/* \file
- *
- * 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 "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/bit_reverse.h"
-#include "util/Error.hxx"
-#include "system/ByteOrder.hxx"
-#include "DsdLib.hxx"
-#include "tag/TagHandler.hxx"
-#include "Log.hxx"
-
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
-struct DsfMetaData {
- unsigned sample_rate, channels;
- bool bitreverse;
- uint64_t chunk_size;
-#ifdef HAVE_ID3TAG
- InputStream::offset_type id3_offset;
- uint64_t id3_size;
-#endif
-};
-
-struct DsfHeader {
- /** DSF header id: "DSD " */
- DsdId id;
- /** DSD chunk size, including id = 28 */
- DsdUint64 size;
- /** total file size */
- DsdUint64 fsize;
- /** pointer to id3v2 metadata, should be at the end of the file */
- DsdUint64 pmeta;
-};
-
-/** DSF file fmt chunk */
-struct DsfFmtChunk {
- /** id: "fmt " */
- DsdId id;
- /** fmt chunk size, including id, normally 52 */
- DsdUint64 size;
- /** 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 */
- DsdUint64 scnt;
- /** block size per channel = 4096 */
- uint32_t block_size;
- /** reserved, should be all zero */
- uint32_t reserved;
-};
-
-struct DsfDataChunk {
- DsdId id;
- /** "data" chunk size, includes header (id+size) */
- DsdUint64 size;
-};
-
-/**
- * Read and parse all needed metadata chunks for DSF files.
- */
-static bool
-dsf_read_metadata(Decoder *decoder, InputStream &is,
- DsfMetaData *metadata)
-{
- DsfHeader dsf_header;
- if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) ||
- !dsf_header.id.Equals("DSD "))
- return false;
-
- const uint64_t chunk_size = dsf_header.size.Read();
- if (sizeof(dsf_header) != chunk_size)
- return false;
-
-#ifdef HAVE_ID3TAG
- const uint64_t metadata_offset = dsf_header.pmeta.Read();
-#endif
-
- /* read the 'fmt ' chunk of the DSF file */
- DsfFmtChunk dsf_fmt_chunk;
- if (!decoder_read_full(decoder, is,
- &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
- !dsf_fmt_chunk.id.Equals("fmt "))
- return false;
-
- const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read();
- if (fmt_chunk_size != sizeof(dsf_fmt_chunk))
- return false;
-
- uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq);
-
- /* for now, only support version 1 of the standard, DSD raw stereo
- files with a sample freq of 2822400 or 5644800 Hz */
-
- if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0
- || dsf_fmt_chunk.channeltype != 2
- || dsf_fmt_chunk.channelnum != 2
- || (samplefreq != 2822400 && samplefreq != 5644800))
- return false;
-
- uint32_t chblksize = FromLE32(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 (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) ||
- !data_chunk.id.Equals("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_chunk.size.Read();
- if (data_size < sizeof(data_chunk))
- return false;
-
- data_size -= sizeof(data_chunk);
-
- /* data_size cannot be bigger or equal to total file size */
- const uint64_t size = (uint64_t)is.GetSize();
- if (data_size >= size)
- return false;
-
- /* use the sample count from the DSF header as the upper
- bound, because some DSF files contain junk at the end of
- the "data" chunk */
- const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read();
- const uint64_t playable_size = samplecnt * 2 / 8;
- if (data_size > playable_size)
- data_size = playable_size;
-
- metadata->chunk_size = data_size;
- 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 = (InputStream::offset_type)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(Decoder &decoder, InputStream &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 >= frame_size) {
- /* see how much aligned data from the remaining chunk
- fits into the local buffer */
- size_t now_size = buffer_size;
- if (chunk_size < (uint64_t)now_size) {
- unsigned now_frames =
- (unsigned)chunk_size / frame_size;
- now_size = now_frames * frame_size;
- }
-
- if (!decoder_read_full(&decoder, is, buffer, now_size))
- return false;
-
- const size_t nbytes = now_size;
- chunk_size -= nbytes;
-
- if (bitreverse)
- bit_reverse_buffer(buffer, buffer + nbytes);
-
- dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes);
-
- const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0);
- switch (cmd) {
- case DecoderCommand::NONE:
- break;
-
- case DecoderCommand::START:
- case DecoderCommand::STOP:
- return false;
-
- case DecoderCommand::SEEK:
-
- /* not implemented yet */
- decoder_seek_error(decoder);
- break;
- }
- }
- return dsdlib_skip(&decoder, is, chunk_size);
-}
-
-static void
-dsf_stream_decode(Decoder &decoder, InputStream &is)
-{
- /* check if it is a proper DSF file */
- DsfMetaData metadata;
- if (!dsf_read_metadata(&decoder, is, &metadata))
- return;
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
- SampleFormat::DSD,
- metadata.channels, error)) {
- LogError(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(InputStream &is,
- gcc_unused const struct tag_handler *handler,
- gcc_unused void *handler_ctx)
-{
- /* check DSF metadata */
- DsfMetaData metadata;
- if (!dsf_read_metadata(nullptr, is, &metadata))
- return false;
-
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
- SampleFormat::DSD,
- metadata.channels, IgnoreError()))
- /* 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",
- nullptr
-};
-
-static const char *const dsf_mime_types[] = {
- "application/x-dsf",
- nullptr
-};
-
-const struct DecoderPlugin 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
deleted file mode 100644
index 921c94698..000000000
--- a/src/decoder/DsfDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_DSF_H
-#define MPD_DECODER_DSF_H
-
-extern const struct DecoderPlugin dsf_decoder_plugin;
-
-#endif
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
deleted file mode 100644
index ae1181b4c..000000000
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FaadDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "DecoderBuffer.hxx"
-#include "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <neaacdec.h>
-
-#include <assert.h>
-#include <string.h>
-#include <unistd.h>
-
-static const unsigned adts_sample_rates[] =
- { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
- 16000, 12000, 11025, 8000, 7350, 0, 0, 0
-};
-
-static constexpr Domain faad_decoder_domain("faad_decoder");
-
-/**
- * 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)
-{
- while (true) {
- size_t length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr || length < 8) {
- /* not enough data yet */
- if (!decoder_buffer_fill(buffer))
- /* 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_clear(buffer);
- continue;
- }
-
- if (p > data) {
- /* discard data before 0xff */
- decoder_buffer_consume(buffer, p - data);
- continue;
- }
-
- /* is it a frame? */
- const size_t 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 */
- if (!decoder_buffer_fill(buffer)) {
- /* not enough data; discard this frame
- to prevent a possible buffer
- overflow */
- decoder_buffer_clear(buffer);
- }
-
- continue;
- }
-
- /* found a full frame! */
- return frame_length;
- }
-}
-
-static float
-adts_song_duration(DecoderBuffer *buffer)
-{
- const InputStream &is = decoder_buffer_get_stream(buffer);
- const bool estimate = !is.CheapSeeking();
- const auto file_size = is.GetSize();
- if (estimate && file_size <= 0)
- return -1;
-
- unsigned sample_rate = 0;
-
- /* Read all frames to ensure correct time and bitrate */
- unsigned frames;
- for (frames = 0;; frames++) {
- const unsigned frame_length = adts_find_frame(buffer);
- if (frame_length == 0)
- break;
-
-
- if (frames == 0) {
- size_t buffer_length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &buffer_length);
- assert(data != nullptr);
- assert(frame_length <= buffer_length);
-
- sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
- if (sample_rate == 0)
- break;
- }
-
- decoder_buffer_consume(buffer, frame_length);
-
- if (estimate && frames == 128) {
- /* if this is a remote file, don't slurp the
- whole file just for checking the song
- duration; instead, stop after some time and
- extrapolate the song duration from what we
- have until now */
-
- const auto offset = is.GetOffset()
- - decoder_buffer_available(buffer);
- if (offset <= 0)
- return -1;
-
- frames = (frames * file_size) / offset;
- break;
- }
- }
-
- if (sample_rate == 0)
- return -1;
-
- float frames_per_second = (float)sample_rate / 1024.0;
- assert(frames_per_second > 0);
-
- return (float)frames / frames_per_second;
-}
-
-static float
-faad_song_duration(DecoderBuffer *buffer, InputStream &is)
-{
- const auto size = is.GetSize();
- const size_t fileread = size >= 0 ? size : 0;
-
- decoder_buffer_fill(buffer);
- size_t length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr)
- return -1;
-
- size_t 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;
-
- const bool success = decoder_buffer_skip(buffer, tagsize) &&
- decoder_buffer_fill(buffer);
- if (!success)
- return -1;
-
- data = (const uint8_t *)decoder_buffer_read(buffer, &length);
- if (data == nullptr)
- return -1;
- }
-
- if (length >= 8 && adts_check_frame(data) > 0) {
- /* obtain the duration from the ADTS header */
-
- if (!is.IsSeekable())
- return -1;
-
- float song_length = adts_song_duration(buffer);
-
- is.LockSeek(tagsize, SEEK_SET, IgnoreError());
- decoder_buffer_clear(buffer);
-
- return song_length;
- } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
- /* obtain the duration from the ADIF header */
- size_t skip_size = (data[4] & 0x80) ? 9 : 0;
-
- if (8 + skip_size > length)
- /* not enough data yet; skip parsing this
- header */
- return -1;
-
- unsigned 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;
-}
-
-static NeAACDecHandle
-faad_decoder_new()
-{
- const NeAACDecHandle decoder = NeAACDecOpen();
-
- NeAACDecConfigurationPtr config =
- NeAACDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
- config->downMatrix = 1;
- config->dontUpSampleImplicitSBR = 0;
- NeAACDecSetConfiguration(decoder, config);
-
- return decoder;
-}
-
-/**
- * Wrapper for NeAACDecInit() which works around some API
- * inconsistencies in libfaad.
- */
-static bool
-faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
- AudioFormat &audio_format, Error &error)
-{
-
- size_t length;
- const unsigned char *data = (const unsigned char *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr) {
- error.Set(faad_decoder_domain, "Empty file");
- return false;
- }
-
- uint8_t channels;
- unsigned long sample_rate;
- long nbytes = NeAACDecInit(decoder,
- /* deconst hack, libfaad requires this */
- const_cast<unsigned char *>(data),
- length,
- &sample_rate, &channels);
- if (nbytes < 0) {
- error.Set(faad_decoder_domain, "Not an AAC stream");
- return false;
- }
-
- decoder_buffer_consume(buffer, nbytes);
-
- return audio_format_init_checked(audio_format, sample_rate,
- SampleFormat::S16, channels, error);
-}
-
-/**
- * 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(InputStream &is)
-{
- DecoderBuffer *const buffer =
- decoder_buffer_new(nullptr, is,
- FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
-
- float length = faad_song_duration(buffer, is);
-
- if (length < 0) {
- AudioFormat audio_format;
-
- NeAACDecHandle decoder = faad_decoder_new();
-
- decoder_buffer_fill(buffer);
-
- if (faad_decoder_init(decoder, buffer, audio_format,
- IgnoreError()))
- 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(InputStream &is)
-{
- float length = faad_get_file_time_float(is);
- if (length < 0)
- return -1;
-
- return int(length + 0.5);
-}
-
-static void
-faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
-{
- DecoderBuffer *const buffer =
- decoder_buffer_new(&mpd_decoder, is,
- FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
-
- const float total_time = faad_song_duration(buffer, is);
-
- /* create the libfaad decoder */
-
- const NeAACDecHandle decoder = faad_decoder_new();
-
- while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
- decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
- adts_find_frame(buffer);
- decoder_buffer_fill(buffer);
- }
-
- /* initialize it */
-
- Error error;
- AudioFormat audio_format;
- if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
- LogError(error);
- NeAACDecClose(decoder);
- decoder_buffer_free(buffer);
- return;
- }
-
- /* initialize the MPD core */
-
- decoder_initialized(mpd_decoder, audio_format, false, total_time);
-
- /* the decoder loop */
-
- DecoderCommand cmd;
- uint16_t bit_rate = 0;
- do {
- /* find the next frame */
-
- const size_t frame_size = adts_find_frame(buffer);
- if (frame_size == 0)
- /* end of file */
- break;
-
- /* decode it */
-
- NeAACDecFrameInfo frame_info;
- const void *const decoded =
- faad_decoder_decode(decoder, buffer, &frame_info);
-
- if (frame_info.error > 0) {
- FormatWarning(faad_decoder_domain,
- "error decoding AAC stream: %s",
- NeAACDecGetErrorMessage(frame_info.error));
- break;
- }
-
- if (frame_info.channels != audio_format.channels) {
- FormatDefault(faad_decoder_domain,
- "channel count changed from %u to %u",
- audio_format.channels, frame_info.channels);
- break;
- }
-
- if (frame_info.samplerate != audio_format.sample_rate) {
- FormatDefault(faad_decoder_domain,
- "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 != DecoderCommand::STOP);
-
- /* cleanup */
-
- NeAACDecClose(decoder);
- decoder_buffer_free(buffer);
-}
-
-static bool
-faad_scan_stream(InputStream &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 DecoderPlugin 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
deleted file mode 100644
index 817927d5e..000000000
--- a/src/decoder/FaadDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FAAD_DECODER_PLUGIN_HXX
-#define MPD_FAAD_DECODER_PLUGIN_HXX
-
-extern const struct DecoderPlugin faad_decoder_plugin;
-
-#endif
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx
deleted file mode 100644
index 9a00bf3c4..000000000
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ /dev/null
@@ -1,741 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/* 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 "tag/TagHandler.hxx"
-#include "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "LogV.hxx"
-
-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>
-
-#if LIBAVUTIL_VERSION_MAJOR >= 53
-#include <libavutil/frame.h>
-#endif
-}
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain ffmpeg_domain("ffmpeg");
-
-/* suppress the ffmpeg compatibility macro */
-#ifdef SampleFormat
-#undef SampleFormat
-#endif
-
-static LogLevel
-import_ffmpeg_level(int level)
-{
- if (level <= AV_LOG_FATAL)
- return LogLevel::ERROR;
-
- if (level <= AV_LOG_WARNING)
- return LogLevel::WARNING;
-
- if (level <= AV_LOG_INFO)
- return LogLevel::INFO;
-
- return LogLevel::DEBUG;
-}
-
-static void
-mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level,
- const char *fmt, va_list vl)
-{
- const AVClass * cls = nullptr;
-
- if (ptr != nullptr)
- cls = *(const AVClass *const*)ptr;
-
- if (cls != nullptr) {
- char domain[64];
- snprintf(domain, sizeof(domain), "%s/%s",
- ffmpeg_domain.GetName(), cls->item_name(ptr));
- const Domain d(domain);
- LogFormatV(d, import_ffmpeg_level(level), fmt, vl);
- }
-}
-
-struct AvioStream {
- Decoder *const decoder;
- InputStream &input;
-
- AVIOContext *io;
-
- unsigned char buffer[8192];
-
- AvioStream(Decoder *_decoder, InputStream &_input)
- :decoder(_decoder), input(_input), io(nullptr) {}
-
- ~AvioStream() {
- if (io != nullptr)
- av_free(io);
- }
-
- bool Open();
-};
-
-static int
-mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
-{
- AvioStream *stream = (AvioStream *)opaque;
-
- return decoder_read(stream->decoder, stream->input,
- (void *)buf, size);
-}
-
-static int64_t
-mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
-{
- AvioStream *stream = (AvioStream *)opaque;
-
- if (whence == AVSEEK_SIZE)
- return stream->input.size;
-
- if (!stream->input.LockSeek(pos, whence, IgnoreError()))
- return -1;
-
- return stream->input.offset;
-}
-
-bool
-AvioStream::Open()
-{
- io = avio_alloc_context(buffer, sizeof(buffer),
- false, this,
- mpd_ffmpeg_stream_read, nullptr,
- input.seekable
- ? mpd_ffmpeg_stream_seek : nullptr);
- return io != nullptr;
-}
-
-/**
- * API compatibility wrapper for av_open_input_stream() and
- * avformat_open_input().
- */
-static int
-mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
- AVIOContext *pb,
- const char *filename,
- AVInputFormat *fmt)
-{
- AVFormatContext *context = avformat_alloc_context();
- if (context == nullptr)
- return AVERROR(ENOMEM);
-
- context->pb = pb;
- *ic_ptr = context;
- return avformat_open_input(ic_ptr, filename, fmt, nullptr);
-}
-
-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;
-}
-
-gcc_const
-static double
-time_from_ffmpeg(int64_t t, const AVRational time_base)
-{
- assert(t != (int64_t)AV_NOPTS_VALUE);
-
- return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
- / (double)1024;
-}
-
-gcc_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);
-}
-
-/**
- * Replace #AV_NOPTS_VALUE with the given fallback.
- */
-static constexpr int64_t
-timestamp_fallback(int64_t t, int64_t fallback)
-{
- return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
- ? t
- : fallback;
-}
-
-/**
- * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
- * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply
- * assume that the stream's start time is zero, which appears to be
- * the best way out of that situation.
- */
-static int64_t
-start_time_fallback(const AVStream &stream)
-{
- return timestamp_fallback(stream.start_time, 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 **output_buffer,
- uint8_t **global_buffer, int *global_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 (data_size <= 0)
- return data_size;
-
- if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
- codec_context->channels > 1) {
- if(*global_buffer_size < data_size) {
- av_freep(global_buffer);
-
- *global_buffer = (uint8_t*)av_malloc(data_size);
-
- if (!*global_buffer)
- /* Not enough memory - shouldn't happen */
- return AVERROR(ENOMEM);
- *global_buffer_size = data_size;
- }
- *output_buffer = *global_buffer;
- copy_interleave_frame2(*output_buffer, frame->extended_data,
- frame->nb_samples,
- codec_context->channels,
- av_get_bytes_per_sample(codec_context->sample_fmt));
- } else {
- *output_buffer = frame->extended_data[0];
- }
-
- return data_size;
-}
-
-static DecoderCommand
-ffmpeg_send_packet(Decoder &decoder, InputStream &is,
- const AVPacket *packet,
- AVCodecContext *codec_context,
- const AVStream *stream,
- AVFrame *frame,
- uint8_t **buffer, int *buffer_size)
-{
- if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) {
- auto start = start_time_fallback(*stream);
- if (packet->pts >= start)
- decoder_timestamp(decoder,
- time_from_ffmpeg(packet->pts - start,
- stream->time_base));
- }
-
- AVPacket packet2 = *packet;
-
- uint8_t *output_buffer;
-
- DecoderCommand cmd = DecoderCommand::NONE;
- while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
- int audio_size = 0;
- int got_frame = 0;
- int len = avcodec_decode_audio4(codec_context,
- frame, &got_frame,
- &packet2);
- if (len >= 0 && got_frame) {
- audio_size = copy_interleave_frame(codec_context,
- frame,
- &output_buffer,
- buffer, buffer_size);
- if (audio_size < 0)
- len = audio_size;
- }
-
- if (len < 0) {
- /* if error, we skip the frame */
- LogDefault(ffmpeg_domain,
- "decoding failed, frame skipped");
- break;
- }
-
- packet2.data += len;
- packet2.size -= len;
-
- if (audio_size <= 0)
- continue;
-
- cmd = decoder_data(decoder, is,
- output_buffer, audio_size,
- codec_context->bit_rate / 1000);
- }
- return cmd;
-}
-
-gcc_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 != nullptr)
- FormatError(ffmpeg_domain,
- "Unsupported libavcodec SampleFormat value: %s (%d)",
- name, sample_fmt);
- else
- FormatError(ffmpeg_domain,
- "Unsupported libavcodec SampleFormat value: %d",
- sample_fmt);
- return SampleFormat::UNDEFINED;
-}
-
-static AVInputFormat *
-ffmpeg_probe(Decoder *decoder, InputStream &is)
-{
- enum {
- BUFFER_SIZE = 16384,
- PADDING = 16,
- };
-
- unsigned char buffer[BUFFER_SIZE];
- size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
- if (nbytes <= PADDING || !is.LockRewind(IgnoreError()))
- return nullptr;
-
- /* 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;
-
- /* new versions of ffmpeg may add new attributes, and leaving
- them uninitialized may crash; hopefully, zero-initializing
- everything we don't know is ok */
- memset(&avpd, 0, sizeof(avpd));
-
- avpd.buf = buffer;
- avpd.buf_size = nbytes;
- avpd.filename = is.uri.c_str();
-
-#ifdef AVPROBE_SCORE_MIME
-#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(56, 5, 1)
- /* this attribute was added in libav/ffmpeg version 11, but
- unfortunately it's "uint8_t" instead of "char", and it's
- not "const" - wtf? */
- avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType());
-#else
- /* API problem fixed in FFmpeg 2.5 */
- avpd.mime_type = is.GetMimeType();
-#endif
-#endif
-
- return av_probe_input_format(&avpd, true);
-}
-
-static void
-ffmpeg_decode(Decoder &decoder, InputStream &input)
-{
- AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
- if (input_format == nullptr)
- return;
-
- FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
- input_format->name, input_format->long_name);
-
- AvioStream stream(&decoder, input);
- if (!stream.Open()) {
- LogError(ffmpeg_domain, "Failed to open stream");
- return;
- }
-
- //ffmpeg works with ours "fileops" helper
- AVFormatContext *format_context = nullptr;
- if (mpd_ffmpeg_open_input(&format_context, stream.io,
- input.uri.c_str(),
- input_format) != 0) {
- LogError(ffmpeg_domain, "Open failed");
- return;
- }
-
- const int find_result =
- avformat_find_stream_info(format_context, nullptr);
- if (find_result < 0) {
- LogError(ffmpeg_domain, "Couldn't find stream info");
- avformat_close_input(&format_context);
- return;
- }
-
- int audio_stream = ffmpeg_find_audio_stream(format_context);
- if (audio_stream == -1) {
- LogError(ffmpeg_domain, "No audio stream inside");
- avformat_close_input(&format_context);
- return;
- }
-
- AVStream *av_stream = format_context->streams[audio_stream];
-
- AVCodecContext *codec_context = av_stream->codec;
-
-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0)
- const AVCodecDescriptor *codec_descriptor =
- avcodec_descriptor_get(codec_context->codec_id);
- if (codec_descriptor != nullptr)
- FormatDebug(ffmpeg_domain, "codec '%s'",
- codec_descriptor->name);
-#else
- if (codec_context->codec_name[0] != 0)
- FormatDebug(ffmpeg_domain, "codec '%s'",
- codec_context->codec_name);
-#endif
-
- AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
-
- if (!codec) {
- LogError(ffmpeg_domain, "Unsupported audio codec");
- avformat_close_input(&format_context);
- return;
- }
-
- const SampleFormat sample_format =
- ffmpeg_sample_format(codec_context->sample_fmt);
- if (sample_format == SampleFormat::UNDEFINED)
- return;
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format,
- codec_context->sample_rate,
- sample_format,
- codec_context->channels, error)) {
- LogError(error);
- avformat_close_input(&format_context);
- 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, nullptr);
- if (open_result < 0) {
- LogError(ffmpeg_domain, "Could not open codec");
- avformat_close_input(&format_context);
- 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);
-
-#if LIBAVUTIL_VERSION_MAJOR >= 53
- AVFrame *frame = av_frame_alloc();
-#else
- AVFrame *frame = avcodec_alloc_frame();
-#endif
- if (!frame) {
- LogError(ffmpeg_domain, "Could not allocate frame");
- avformat_close_input(&format_context);
- return;
- }
-
- uint8_t *interleaved_buffer = nullptr;
- int interleaved_buffer_size = 0;
-
- DecoderCommand 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,
- frame,
- &interleaved_buffer, &interleaved_buffer_size);
- else
- cmd = decoder_get_command(decoder);
-
- av_free_packet(&packet);
-
- if (cmd == DecoderCommand::SEEK) {
- int64_t where =
- time_to_ffmpeg(decoder_seek_where(decoder),
- av_stream->time_base) +
- start_time_fallback(*av_stream);
-
- if (av_seek_frame(format_context, audio_stream, where,
- AVSEEK_FLAG_ANY) < 0)
- decoder_seek_error(decoder);
- else {
- avcodec_flush_buffers(codec_context);
- decoder_command_finished(decoder);
- }
- }
- } while (cmd != DecoderCommand::STOP);
-
-#if LIBAVUTIL_VERSION_MAJOR >= 53
- av_frame_free(&frame);
-#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
- avcodec_free_frame(&frame);
-#else
- av_freep(&frame);
-#endif
- av_freep(&interleaved_buffer);
-
- avcodec_close(codec_context);
- avformat_close_input(&format_context);
-}
-
-//no tag reading in ffmpeg, check if playable
-static bool
-ffmpeg_scan_stream(InputStream &is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- AVInputFormat *input_format = ffmpeg_probe(nullptr, is);
- if (input_format == nullptr)
- return false;
-
- AvioStream stream(nullptr, is);
- if (!stream.Open())
- return false;
-
- AVFormatContext *f = nullptr;
- if (mpd_ffmpeg_open_input(&f, stream.io, is.uri.c_str(),
- input_format) != 0)
- return false;
-
- const int find_result =
- avformat_find_stream_info(f, nullptr);
- if (find_result < 0) {
- avformat_close_input(&f);
- return false;
- }
-
- if (f->duration != (int64_t)AV_NOPTS_VALUE)
- 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);
- 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", "opus", "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",
- nullptr
-};
-
-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/aacp",
- "audio/ac3",
- "audio/aiff"
- "audio/amr",
- "audio/basic",
- "audio/flac",
- "audio/m4a",
- "audio/mp4",
- "audio/mpeg",
- "audio/musepack",
- "audio/ogg",
- "audio/opus",
- "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",
-
- nullptr
-};
-
-const struct DecoderPlugin 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
deleted file mode 100644
index 23bf74fce..000000000
--- a/src/decoder/FfmpegDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_FFMPEG_HXX
-#define MPD_DECODER_FFMPEG_HXX
-
-extern const struct DecoderPlugin ffmpeg_decoder_plugin;
-
-#endif
diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx
deleted file mode 100644
index 6e92b4a13..000000000
--- a/src/decoder/FfmpegMetaData.cxx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/* necessary because libavutil/common.h uses UINT64_C */
-#define __STDC_CONSTANT_MACROS
-
-#include "config.h"
-#include "FfmpegMetaData.hxx"
-#include "tag/TagTable.hxx"
-#include "tag/TagHandler.hxx"
-
-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 */
- { nullptr, TAG_NUM_OF_ITEM_TYPES }
-};
-
-static void
-ffmpeg_copy_metadata(TagType type,
- AVDictionary *m, const char *name,
- const struct tag_handler *handler, void *handler_ctx)
-{
- AVDictionaryEntry *mt = nullptr;
-
- while ((mt = av_dict_get(m, name, mt, 0)) != nullptr)
- 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 = nullptr;
-
- while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr)
- 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(TagType(i), dict, tag_item_names[i],
- handler, handler_ctx);
-
- for (const struct tag_table *i = ffmpeg_tags;
- i->name != nullptr; ++i)
- ffmpeg_copy_metadata(i->type, dict, i->name,
- handler, handler_ctx);
-
- if (handler->pair != nullptr)
- ffmpeg_scan_pairs(dict, handler, handler_ctx);
-}
diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx
deleted file mode 100644
index 998cdf5a8..000000000
--- a/src/decoder/FfmpegMetaData.hxx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FFMPEG_METADATA_HXX
-#define MPD_FFMPEG_METADATA_HXX
-
-extern "C" {
-#include <libavutil/dict.h>
-}
-
-/* suppress the ffmpeg compatibility macro */
-#ifdef SampleFormat
-#undef SampleFormat
-#endif
-
-struct tag_handler;
-
-void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const tag_handler *handler, void *handler_ctx);
-
-#endif
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx
deleted file mode 100644
index e4b906c12..000000000
--- a/src/decoder/FlacCommon.cxx
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Common data structures and functions used by FLAC and OggFLAC
- */
-
-#include "config.h"
-#include "FlacCommon.hxx"
-#include "FlacMetadata.hxx"
-#include "FlacPcm.hxx"
-#include "MixRampInfo.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-flac_data::flac_data(Decoder &_decoder,
- InputStream &_input_stream)
- :FlacInput(_input_stream, &_decoder),
- 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;
-
- Error error;
- if (!audio_format_init_checked(data->audio_format,
- stream_info->sample_rate,
- flac_sample_format(stream_info->bits_per_sample),
- stream_info->channels, error)) {
- LogError(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;
-
- ReplayGainInfo rgi;
-
- 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);
-
- decoder_mixramp(data->decoder, flac_parse_mixramp(block));
-
- 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;
-
- Error error;
- if (!audio_format_init_checked(data->audio_format,
- header->sample_rate,
- flac_sample_format(header->bits_per_sample),
- header->channels, error)) {
- LogError(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)
-{
- 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;
-
- auto cmd = decoder_data(data->decoder, data->input_stream,
- buffer, buffer_size,
- bit_rate);
- data->next_frame += frame->header.blocksize;
- switch (cmd) {
- case DecoderCommand::NONE:
- case DecoderCommand::START:
- break;
-
- case DecoderCommand::STOP:
- return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
-
- case DecoderCommand::SEEK:
- return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
- }
-
- return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
-}
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx
deleted file mode 100644
index de000dfa1..000000000
--- a/src/decoder/FlacCommon.hxx
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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>
-
-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;
-
- Decoder &decoder;
- InputStream &input_stream;
-
- Tag tag;
-
- flac_data(Decoder &decoder, InputStream &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
deleted file mode 100644
index 1b5734434..000000000
--- a/src/decoder/FlacDecoderPlugin.cxx
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "FlacDecoderPlugin.h"
-#include "FlacDomain.hxx"
-#include "FlacCommon.hxx"
-#include "FlacMetadata.hxx"
-#include "OggCodec.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
-#error libFLAC is too old
-#endif
-
-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;
- }
-
- LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
-}
-
-static void flacMetadata(gcc_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)) {
- FormatDebug(flac_domain,
- "Failed to read FLAC tags: %s",
- chain.GetStatusString());
- return false;
- }
-
- chain.Scan(handler, handler_ctx);
- return true;
-}
-
-static bool
-flac_scan_stream(InputStream &is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- FlacMetadataChain chain;
- if (!chain.Read(is)) {
- FormatDebug(flac_domain,
- "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) {
- LogError(flac_domain,
- "FLAC__stream_decoder_new() failed");
- return nullptr;
- }
-
- if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- LogDebug(flac_domain,
- "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)) {
- LogWarning(flac_domain, "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)
-{
- Decoder &decoder = data->decoder;
-
- data->first_frame = t_start;
-
- while (true) {
- DecoderCommand cmd;
- 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 == DecoderCommand::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 == DecoderCommand::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) == DecoderCommand::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(Decoder &decoder,
- InputStream &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);
- LogWarning(flac_domain,
- 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(Decoder &decoder, InputStream &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)) {
- FormatDebug(flac_domain,
- "Failed to read OggFLAC tags: %s",
- chain.GetStatusString());
- return false;
- }
-
- chain.Scan(handler, handler_ctx);
- return true;
-}
-
-static bool
-oggflac_scan_stream(InputStream &is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- FlacMetadataChain chain;
- if (!chain.ReadOgg(is)) {
- FormatDebug(flac_domain,
- "Failed to read OggFLAC tags: %s",
- chain.GetStatusString());
- return false;
- }
-
- chain.Scan(handler, handler_ctx);
- return true;
-}
-
-static void
-oggflac_decode(Decoder &decoder, InputStream &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.LockRewind(IgnoreError());
-
- 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 DecoderPlugin 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 DecoderPlugin 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
deleted file mode 100644
index 936423fbf..000000000
--- a/src/decoder/FlacDecoderPlugin.h
+++ /dev/null
@@ -1,26 +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_FLAC_H
-#define MPD_DECODER_FLAC_H
-
-extern const struct DecoderPlugin flac_decoder_plugin;
-extern const struct DecoderPlugin oggflac_decoder_plugin;
-
-#endif
diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/FlacDomain.cxx
deleted file mode 100644
index 5858004de..000000000
--- a/src/decoder/FlacDomain.cxx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FlacDomain.hxx"
-#include "util/Domain.hxx"
-
-const Domain flac_domain("flac");
diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/FlacDomain.hxx
deleted file mode 100644
index cf357332f..000000000
--- a/src/decoder/FlacDomain.hxx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FLAC_DOMAIN_HXX
-#define MPD_FLAC_DOMAIN_HXX
-
-#include "check.h"
-
-extern const class Domain flac_domain;
-
-#endif
diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/FlacIOHandle.cxx
deleted file mode 100644
index b471ecf64..000000000
--- a/src/decoder/FlacIOHandle.cxx
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FlacIOHandle.hxx"
-#include "util/Error.hxx"
-#include "Compiler.h"
-
-#include <errno.h>
-
-static size_t
-FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
-{
- InputStream *is = (InputStream *)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!) */
-
- Error error;
- while (p < end) {
- size_t nbytes = is->LockRead(p, end - p, error);
- if (nbytes == 0) {
- if (!error.IsDefined())
- /* end of file */
- break;
-
- if (error.IsDomain(errno_domain))
- errno = error.GetCode();
- else
- /* just some random non-zero
- errno value */
- errno = EINVAL;
- 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)
-{
- InputStream *is = (InputStream *)handle;
-
- Error error;
- return is->LockSeek(offset, whence, error) ? 0 : -1;
-}
-
-static FLAC__int64
-FlacIOTell(FLAC__IOHandle handle)
-{
- InputStream *is = (InputStream *)handle;
-
- return is->offset;
-}
-
-static int
-FlacIOEof(FLAC__IOHandle handle)
-{
- InputStream *is = (InputStream *)handle;
-
- return is->LockIsEOF();
-}
-
-static int
-FlacIOClose(gcc_unused FLAC__IOHandle handle)
-{
- /* no-op because the libFLAC caller is repsonsible for closing
- the #InputStream */
-
- 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
deleted file mode 100644
index b6e563fa3..000000000
--- a/src/decoder/FlacIOHandle.hxx
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FLAC_IO_HANDLE_HXX
-#define MPD_FLAC_IO_HANDLE_HXX
-
-#include "Compiler.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(InputStream &is)
-{
- return (FLAC__IOHandle)&is;
-}
-
-static inline const FLAC__IOCallbacks &
-GetFlacIOCallbacks(const InputStream &is)
-{
- return is.seekable
- ? flac_io_callbacks_seekable
- : flac_io_callbacks;
-}
-
-#endif
diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx
deleted file mode 100644
index ce193101d..000000000
--- a/src/decoder/FlacInput.cxx
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FlacInput.hxx"
-#include "FlacDomain.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-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.LockIsEOF() ||
- (decoder != nullptr &&
- decoder_get_command(*decoder) != DecoderCommand::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;
-
- ::Error error;
- if (!input_stream.LockSeek(absolute_byte_offset, SEEK_SET, error)) {
- LogError(error);
- 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) != DecoderCommand::NONE &&
- decoder_get_command(*decoder) != DecoderCommand::SEEK) ||
- input_stream.LockIsEOF();
-}
-
-void
-FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
-{
- if (decoder == nullptr ||
- decoder_get_command(*decoder) != DecoderCommand::STOP)
- LogWarning(flac_domain,
- 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
deleted file mode 100644
index ddd5649f8..000000000
--- a/src/decoder/FlacInput.hxx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FLAC_INPUT_HXX
-#define MPD_FLAC_INPUT_HXX
-
-#include <FLAC/stream_decoder.h>
-
-struct Decoder;
-struct InputStream;
-
-/**
- * This class wraps an #InputStream in libFLAC stream decoder
- * callbacks.
- */
-class FlacInput {
- Decoder *const decoder;
-
- InputStream &input_stream;
-
-public:
- FlacInput(InputStream &_input_stream,
- 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
deleted file mode 100644
index 17cc4cd8d..000000000
--- a/src/decoder/FlacMetadata.cxx
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FlacMetadata.hxx"
-#include "XiphTags.hxx"
-#include "MixRampInfo.hxx"
-#include "tag/Tag.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/TagTable.hxx"
-#include "tag/TagBuilder.hxx"
-#include "ReplayGainInfo.hxx"
-#include "util/ASCII.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.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(ReplayGainInfo &rgi,
- const FLAC__StreamMetadata *block)
-{
- rgi.Clear();
-
- bool found = false;
- 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;
-}
-
-gcc_pure
-static std::string
-flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt)
-{
- int offset;
- size_t pos;
- int len;
- const unsigned char *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
- return std::string();
-
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
- return std::string();
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- return std::string((const char *)p, len);
-}
-
-MixRampInfo
-flac_parse_mixramp(const FLAC__StreamMetadata *block)
-{
- MixRampInfo mix_ramp;
- mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start"));
- mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end"));
- return mix_ramp;
-}
-
-/**
- * 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 ||
- !StringEqualsCaseASCII(comment, name, name_length))
- 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, TagType tag_type,
- const struct tag_handler *handler, void *handler_ctx)
-{
- const char *value;
- size_t value_length;
-
- value = flac_comment_value(entry, name, &value_length);
- 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], (TagType)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)
-{
- TagBuilder tag_builder;
- flac_scan_comments(comment, &add_tag_handler, &tag_builder);
- tag_builder.Commit(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
deleted file mode 100644
index 96c61b8e6..000000000
--- a/src/decoder/FlacMetadata.hxx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FLAC_METADATA_H
-#define MPD_FLAC_METADATA_H
-
-#include "Compiler.h"
-#include "FlacIOHandle.hxx"
-
-#include <FLAC/metadata.h>
-
-#include <assert.h>
-
-class MixRampInfo;
-
-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(InputStream &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(InputStream &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 ReplayGainInfo;
-
-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(ReplayGainInfo &rgi,
- const FLAC__StreamMetadata *block);
-
-MixRampInfo
-flac_parse_mixramp(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
deleted file mode 100644
index 569879371..000000000
--- a/src/decoder/FlacPcm.cxx
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
deleted file mode 100644
index 6cb2d5062..000000000
--- a/src/decoder/FlacPcm.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index fa946f219..000000000
--- a/src/decoder/FluidsynthDecoderPlugin.cxx
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FluidsynthDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "Log.hxx"
-
-#include <fluidsynth.h>
-
-static constexpr Domain fluidsynth_domain("fluidsynth");
-
-static unsigned sample_rate;
-static const char *soundfont_path;
-
-/**
- * Convert a fluidsynth log level to a GLib log level.
- */
-static LogLevel
-fluidsynth_level_to_mpd(enum fluid_log_level level)
-{
- switch (level) {
- case FLUID_PANIC:
- case FLUID_ERR:
- return LogLevel::ERROR;
-
- case FLUID_WARN:
- return LogLevel::WARNING;
-
- case FLUID_INFO:
- return LogLevel::INFO;
-
- case FLUID_DBG:
- case LAST_LOG_LEVEL:
- return LogLevel::DEBUG;
- }
-
- /* invalid fluidsynth log level */
- return LogLevel::INFO;
-}
-
-/**
- * The fluidsynth logging callback. It forwards messages to the GLib
- * logging library.
- */
-static void
-fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data)
-{
- Log(fluidsynth_domain,
- fluidsynth_level_to_mpd(fluid_log_level(level)),
- message);
-}
-
-static bool
-fluidsynth_init(const config_param &param)
-{
- Error error;
-
- sample_rate = param.GetBlockValue("sample_rate", 48000u);
- if (!audio_check_sample_rate(sample_rate, error)) {
- LogError(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(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;
-
- /* 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) {
- LogWarning(fluidsynth_domain, "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) {
- LogWarning(fluidsynth_domain, "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) {
- LogWarning(fluidsynth_domain, "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);
-
- DecoderCommand cmd;
- while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
- int16_t buffer[2048];
- const unsigned max_frames = ARRAY_SIZE(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 != DecoderCommand::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,
- gcc_unused const struct tag_handler *handler,
- gcc_unused void *handler_ctx)
-{
- return fluid_is_midifile(file);
-}
-
-static const char *const fluidsynth_suffixes[] = {
- "mid",
- nullptr
-};
-
-const struct DecoderPlugin 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
deleted file mode 100644
index 9771898a5..000000000
--- a/src/decoder/FluidsynthDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_FLUIDSYNTH_HXX
-#define MPD_DECODER_FLUIDSYNTH_HXX
-
-extern const struct DecoderPlugin fluidsynth_decoder_plugin;
-
-#endif
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx
deleted file mode 100644
index 9c9b19478..000000000
--- a/src/decoder/GmeDecoderPlugin.cxx
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "GmeDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/FormatString.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <gme/gme.h>
-
-#define SUBTUNE_PREFIX "tune_"
-
-static constexpr Domain gme_domain("gme");
-
-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[64];
- snprintf(pat, sizeof(pat), "%s%s",
- "*/" SUBTUNE_PREFIX "???.",
- subtune_suffix);
- GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
- if (!g_pattern_match(path_with_subtune,
- strlen(path_container), path_container, nullptr)) {
- g_pattern_spec_free(path_with_subtune);
- return path_container;
- }
-
- 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[64];
- snprintf(pat, sizeof(pat), "%s%s",
- "*/" SUBTUNE_PREFIX "???.",
- subtune_suffix);
- GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
-
- if (g_pattern_match(path_with_subtune,
- strlen(path_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) {
- LogWarning(gme_domain, gme_err);
- return nullptr;
- }
-
- const unsigned num_songs = gme_track_count(emu);
- gme_delete(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){
- return FormatNew(SUBTUNE_PREFIX "%03u.%s",
- tnum, subtune_suffix);
- } else
- return nullptr;
-}
-
-static void
-gme_file_decode(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) {
- LogWarning(gme_domain, gme_err);
- return;
- }
-
- gme_info_t *ti;
- const int song_num = get_song_num(path_fs);
- gme_err = gme_track_info(emu, &ti, song_num);
- if (gme_err != nullptr) {
- LogWarning(gme_domain, gme_err);
- gme_delete(emu);
- return;
- }
-
- const float song_len = ti->length > 0
- ? ti->length / 1000.0
- : -1.0;
-
- /* initialize the MPD decoder */
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
- SampleFormat::S16, GME_CHANNELS,
- error)) {
- LogError(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)
- LogWarning(gme_domain, gme_err);
-
- if (ti->length > 0)
- gme_set_fade(emu, ti->length);
-
- /* play */
- DecoderCommand cmd;
- do {
- short buf[GME_BUFFER_SAMPLES];
- gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
- if (gme_err != nullptr) {
- LogWarning(gme_domain, gme_err);
- return;
- }
-
- cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0);
- if (cmd == DecoderCommand::SEEK) {
- float where = decoder_seek_where(decoder);
- gme_err = gme_seek(emu, int(where * 1000));
- if (gme_err != nullptr)
- LogWarning(gme_domain, gme_err);
- decoder_command_finished(decoder);
- }
-
- if (gme_track_ended(emu))
- break;
- } while (cmd != DecoderCommand::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) {
- LogWarning(gme_domain, gme_err);
- return false;
- }
-
- const int song_num = get_song_num(path_fs);
-
- gme_info_t *ti;
- gme_err = gme_track_info(emu, &ti, song_num);
- if (gme_err != nullptr) {
- LogWarning(gme_domain, gme_err);
- gme_delete(emu);
- return false;
- }
-
- assert(ti != nullptr);
-
- if (ti->length > 0)
- tag_handler_invoke_duration(handler, handler_ctx,
- ti->length / 1000);
-
- if (ti->song != nullptr) {
- if (gme_track_count(emu) > 1) {
- /* start numbering subtunes from 1 */
- char tag_title[1024];
- snprintf(tag_title, sizeof(tag_title),
- "%s (%d/%d)",
- ti->song, song_num + 1,
- gme_track_count(emu));
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, 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 DecoderPlugin gme_decoder_plugin;
-const struct DecoderPlugin 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
deleted file mode 100644
index e46dc766a..000000000
--- a/src/decoder/GmeDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_GME_HXX
-#define MPD_DECODER_GME_HXX
-
-extern const struct DecoderPlugin gme_decoder_plugin;
-
-#endif
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx
deleted file mode 100644
index 6f619b34b..000000000
--- a/src/decoder/MadDecoderPlugin.cxx
+++ /dev/null
@@ -1,1155 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MadDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "ConfigGlobal.hxx"
-#include "tag/TagId3.hxx"
-#include "tag/TagRva2.hxx"
-#include "tag/TagHandler.hxx"
-#include "CheckAudioFormat.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <glib.h>
-#include <mad.h>
-
-#ifdef HAVE_ID3TAG
-#include <id3tag.h>
-#endif
-
-#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 constexpr Domain mad_domain("mad");
-
-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;
- Decoder *const decoder;
- InputStream &input_stream;
- enum mad_layer layer;
-
- MadDecoder(Decoder *decoder, InputStream &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
- InputStream::offset_type ThisFrameOffset() const;
-
- gcc_pure
- InputStream::offset_type 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().
- */
- DecoderCommand SendPCM(unsigned i, unsigned pcm_length);
-
- /**
- * Synthesize the current frame and send it via
- * decoder_data().
- */
- DecoderCommand SyncAndSend();
-
- bool Read();
-};
-
-MadDecoder::MadDecoder(Decoder *_decoder,
- InputStream &_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)
-{
- Error error;
- if (!input_stream.LockSeek(offset, SEEK_SET, error))
- 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(ReplayGainInfo &rgi,
- struct id3_tag *tag)
-{
- int i;
- char *key;
- char *value;
- struct id3_frame *frame;
- bool found = false;
-
- rgi.Clear();
-
- 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 (StringEqualsCaseASCII(key, "replaygain_track_gain")) {
- rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value);
- found = true;
- } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) {
- rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
- found = true;
- } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) {
- rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value);
- found = true;
- } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) {
- rgi.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, rgi);
-}
-#endif
-
-#ifdef HAVE_ID3TAG
-gcc_pure
-static MixRampInfo
-parse_id3_mixramp(struct id3_tag *tag)
-{
- int i;
- char *key;
- char *value;
- struct id3_frame *frame;
-
- MixRampInfo result;
-
- 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 (StringEqualsCaseASCII(key, "mixramp_start")) {
- result.SetStart(value);
- } else if (StringEqualsCaseASCII(key, "mixramp_end")) {
- result.SetEnd(value);
- }
-
- free(key);
- free(value);
- }
-
- return result;
-}
-#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);
-
- if (!decoder_read_full(decoder, input_stream,
- allocated + count, tagsize - count)) {
- LogDebug(mad_domain, "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) {
- ReplayGainInfo rgi;
-
- if (parse_id3_replay_gain_info(rgi, id3_tag)) {
- decoder_replay_gain(*decoder, &rgi);
- found_replay_gain = true;
- }
-
- decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag));
- }
-
- 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);
- decoder_skip(decoder, input_stream, tagsize - count);
- }
-#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 {
- FormatWarning(mad_domain,
- "unrecoverable frame level error: %s",
- 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 {
- FormatWarning(mad_domain,
- "unrecoverable frame level error: %s",
- 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;
-
- FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")",
- 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 */
- FormatDebug(mad_domain, "LAME peak found: %f", 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;
- FormatDebug(mad_domain, "LAME track gain found: %f",
- 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;
- FormatDebug(mad_domain, "LAME album gain found: %f",
- 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);
-
- FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i",
- 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 InputStream::offset_type
-MadDecoder::ThisFrameOffset() const
-{
- auto offset = input_stream.GetOffset();
-
- if (stream.this_frame != nullptr)
- offset -= stream.bufend - stream.this_frame;
- else
- offset -= stream.bufend - stream.buffer;
-
- return offset;
-}
-
-inline InputStream::offset_type
-MadDecoder::RestIncludingThisFrame() const
-{
- return input_stream.GetSize() - ThisFrameOffset();
-}
-
-inline void
-MadDecoder::FileSizeToSongLength()
-{
- InputStream::offset_type 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.IsSeekable()) {
- 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) {
- ReplayGainInfo rgi;
- rgi.Clear();
- 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) {
- FormatWarning(mad_domain,
- "mp3 file header indicates too many frames: %lu",
- 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(InputStream &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;
-}
-
-DecoderCommand
-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) {
- 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);
-
- auto cmd = decoder_data(*decoder, input_stream, output_buffer,
- sizeof(output_buffer[0]) * num_samples,
- bit_rate / 1000);
- if (cmd != DecoderCommand::NONE)
- return cmd;
- }
-
- return DecoderCommand::NONE;
-}
-
-inline DecoderCommand
-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 DecoderCommand::NONE;
- } else if ((drop_end_frames > 0) &&
- (current_frame == (max_frames + 1 - drop_end_frames))) {
- /* stop decoding, effectively dropping all remaining
- frames */
- return DecoderCommand::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;
- }
-
- auto cmd = SendPCM(i, pcm_length);
- if (cmd != DecoderCommand::NONE)
- return cmd;
-
- if (drop_end_samples &&
- (current_frame == max_frames - drop_end_frames))
- /* stop decoding, effectively dropping
- * all remaining samples */
- return DecoderCommand::STOP;
-
- return DecoderCommand::NONE;
-}
-
-inline bool
-MadDecoder::Read()
-{
- enum mp3_action ret;
-
- UpdateTimerNextFrame();
-
- switch (mute_frame) {
- DecoderCommand cmd;
-
- 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 == DecoderCommand::SEEK) {
- unsigned long j;
-
- assert(input_stream.IsSeekable());
-
- 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 != DecoderCommand::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(Decoder &decoder, InputStream &input_stream)
-{
- MadDecoder data(&decoder, input_stream);
-
- Tag *tag = nullptr;
- if (!data.DecodeFirstFrame(&tag)) {
- delete tag;
-
- if (decoder_get_command(decoder) == DecoderCommand::NONE)
- LogError(mad_domain,
- "Input does not appear to be a mp3 bit stream");
- return;
- }
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format,
- data.frame.header.samplerate,
- SampleFormat::S24_P32,
- MAD_NCHANNELS(&data.frame.header),
- error)) {
- LogError(error);
- delete tag;
- return;
- }
-
- decoder_initialized(decoder, audio_format,
- input_stream.IsSeekable(),
- 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(InputStream &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 DecoderPlugin 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
deleted file mode 100644
index 450323670..000000000
--- a/src/decoder/MadDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_MAD_HXX
-#define MPD_DECODER_MAD_HXX
-
-extern const struct DecoderPlugin mad_decoder_plugin;
-
-#endif
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx
deleted file mode 100644
index 34381aafa..000000000
--- a/src/decoder/MikmodDecoderPlugin.cxx
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MikmodDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "tag/TagHandler.hxx"
-#include "system/FatalError.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <mikmod.h>
-#include <assert.h>
-
-static constexpr Domain mikmod_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 bool mikmod_loop;
-static unsigned mikmod_sample_rate;
-
-static bool
-mikmod_decoder_init(const config_param &param)
-{
- static char params[] = "";
-
- mikmod_loop = param.GetBlockValue("loop", false);
- mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u);
- if (!audio_valid_sample_rate(mikmod_sample_rate))
- FormatFatalError("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)) {
- FormatError(mikmod_domain,
- "Could not init MikMod: %s",
- MikMod_strerror(MikMod_errno));
- return false;
- }
-
- return true;
-}
-
-static void
-mikmod_decoder_finish(void)
-{
- MikMod_Exit();
-}
-
-static void
-mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs)
-{
- /* deconstify the path because libmikmod wants a non-const
- string pointer */
- char *const path2 = const_cast<char *>(path_fs);
-
- MODULE *handle;
- int ret;
- SBYTE buffer[MIKMOD_FRAME_SIZE];
-
- handle = Player_Load(path2, 128, 0);
-
- if (handle == nullptr) {
- FormatError(mikmod_domain,
- "failed to open mod: %s", path_fs);
- return;
- }
-
- handle->loop = mikmod_loop;
-
- const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
- assert(audio_format.IsValid());
-
- decoder_initialized(decoder, audio_format, false, 0);
-
- Player_Start(handle);
-
- DecoderCommand cmd = DecoderCommand::NONE;
- while (cmd == DecoderCommand::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)
-{
- /* deconstify the path because libmikmod wants a non-const
- string pointer */
- char *const path2 = const_cast<char *>(path_fs);
-
- MODULE *handle = Player_Load(path2, 128, 0);
-
- if (handle == nullptr) {
- FormatDebug(mikmod_domain,
- "Failed to open file: %s", path_fs);
- return false;
- }
-
- Player_Free(handle);
-
- char *title = Player_LoadTitle(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 DecoderPlugin 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
deleted file mode 100644
index d25c5f6e7..000000000
--- a/src/decoder/MikmodDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_MIKMOD_HXX
-#define MPD_DECODER_MIKMOD_HXX
-
-extern const struct DecoderPlugin mikmod_decoder_plugin;
-
-#endif
diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx
deleted file mode 100644
index e75f5479c..000000000
--- a/src/decoder/ModplugDecoderPlugin.cxx
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ModplugDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "tag/TagHandler.hxx"
-#include "system/FatalError.hxx"
-#include "util/WritableBuffer.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <libmodplug/modplug.h>
-
-
-#include <assert.h>
-
-static constexpr Domain modplug_domain("modplug");
-
-static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
-static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024;
-static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024;
-
-static int modplug_loop_count;
-
-static bool
-modplug_decoder_init(const config_param &param)
-{
- modplug_loop_count = param.GetBlockValue("loop_count", 0);
- if (modplug_loop_count < -1)
- FormatFatalError("Invalid loop count in line %d: %i",
- param.line, modplug_loop_count);
-
- return true;
-}
-
-static WritableBuffer<uint8_t>
-mod_loadfile(Decoder *decoder, InputStream &is)
-{
- const InputStream::offset_type size = is.GetSize();
-
- if (size == 0) {
- LogWarning(modplug_domain, "file is empty");
- return { nullptr, 0 };
- }
-
- if (size > MODPLUG_FILE_LIMIT) {
- LogWarning(modplug_domain, "file too large");
- return { nullptr, 0 };
- }
-
- //known/unknown size, preallocate array, lets read in chunks
-
- const bool is_stream = size < 0;
-
- WritableBuffer<uint8_t> buffer;
- buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size;
- buffer.data = new uint8_t[buffer.size];
-
- uint8_t *const end = buffer.end();
- uint8_t *p = buffer.begin();
-
- while (true) {
- size_t ret = decoder_read(decoder, is, p, end - p);
- if (ret == 0) {
- if (is.LockIsEOF())
- /* end of file */
- break;
-
- /* I/O error - skip this song */
- delete[] buffer.data;
- buffer.data = nullptr;
- return buffer;
- }
-
- p += ret;
- if (p == end) {
- if (!is_stream)
- break;
-
- LogWarning(modplug_domain, "stream too large");
- delete[] buffer.data;
- buffer.data = nullptr;
- return buffer;
- }
- }
-
- buffer.size = p - buffer.data;
- return buffer;
-}
-
-static ModPlugFile *
-LoadModPlugFile(Decoder *decoder, InputStream &is)
-{
- const auto buffer = mod_loadfile(decoder, is);
- if (buffer.IsNull()) {
- LogWarning(modplug_domain, "could not load stream");
- return nullptr;
- }
-
- ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size);
- delete[] buffer.data;
- return f;
-}
-
-static void
-mod_decode(Decoder &decoder, InputStream &is)
-{
- ModPlug_Settings settings;
- int ret;
- char audio_buffer[MODPLUG_FRAME_SIZE];
-
- ModPlug_GetSettings(&settings);
- /* alter setting */
- settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
- settings.mChannels = 2;
- settings.mBits = 16;
- settings.mFrequency = 44100;
- settings.mLoopCount = modplug_loop_count;
- /* insert more setting changes here */
- ModPlug_SetSettings(&settings);
-
- ModPlugFile *f = LoadModPlugFile(&decoder, is);
- if (f == nullptr) {
- LogWarning(modplug_domain, "could not decode stream");
- return;
- }
-
- static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2);
- assert(audio_format.IsValid());
-
- decoder_initialized(decoder, audio_format,
- is.IsSeekable(),
- ModPlug_GetLength(f) / 1000.0);
-
- DecoderCommand cmd;
- 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 == DecoderCommand::SEEK) {
- float where = decoder_seek_where(decoder);
-
- ModPlug_Seek(f, (int)(where * 1000.0));
-
- decoder_command_finished(decoder);
- }
-
- } while (cmd != DecoderCommand::STOP);
-
- ModPlug_Unload(f);
-}
-
-static bool
-modplug_scan_stream(InputStream &is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- ModPlugFile *f = LoadModPlugFile(nullptr, is);
- 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 DecoderPlugin modplug_decoder_plugin = {
- "modplug",
- modplug_decoder_init,
- nullptr,
- mod_decode,
- nullptr,
- nullptr,
- modplug_scan_stream,
- nullptr,
- mod_suffixes,
- nullptr,
-};
diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/ModplugDecoderPlugin.hxx
deleted file mode 100644
index 4cd9f5b25..000000000
--- a/src/decoder/ModplugDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_MODPLUG_HXX
-#define MPD_DECODER_MODPLUG_HXX
-
-extern const struct DecoderPlugin modplug_decoder_plugin;
-
-#endif
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx
deleted file mode 100644
index dc258623c..000000000
--- a/src/decoder/MpcdecDecoderPlugin.cxx
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MpcdecDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "Log.hxx"
-
-#include <mpc/mpcdec.h>
-
-#include <assert.h>
-#include <unistd.h>
-#include <math.h>
-
-struct mpc_decoder_data {
- InputStream &is;
- Decoder *decoder;
-
- mpc_decoder_data(InputStream &_is, Decoder *_decoder)
- :is(_is), decoder(_decoder) {}
-};
-
-static constexpr Domain mpcdec_domain("mpcdec");
-
-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 data->is.LockSeek(offset, SEEK_SET, IgnoreError());
-}
-
-static mpc_int32_t
-mpc_tell_cb(mpc_reader *reader)
-{
- struct mpc_decoder_data *data =
- (struct mpc_decoder_data *)reader->data;
-
- return (long)data->is.GetOffset();
-}
-
-static mpc_bool_t
-mpc_canseek_cb(mpc_reader *reader)
-{
- struct mpc_decoder_data *data =
- (struct mpc_decoder_data *)reader->data;
-
- return data->is.IsSeekable();
-}
-
-static mpc_int32_t
-mpc_getsize_cb(mpc_reader *reader)
-{
- struct mpc_decoder_data *data =
- (struct mpc_decoder_data *)reader->data;
-
- return data->is.GetSize();
-}
-
-/* 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(Decoder &mpd_decoder, InputStream &is)
-{
- MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
-
- mpc_decoder_data data(is, &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) != DecoderCommand::STOP)
- LogWarning(mpcdec_domain,
- "Not a valid musepack stream");
- return;
- }
-
- mpc_streaminfo info;
- mpc_demux_get_info(demux, &info);
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, info.sample_freq,
- SampleFormat::S24_P32,
- info.channels, error)) {
- LogError(error);
- mpc_demux_exit(demux);
- return;
- }
-
- ReplayGainInfo rgi;
- rgi.Clear();
- rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
- rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
- rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
- rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
-
- decoder_replay_gain(mpd_decoder, &rgi);
-
- decoder_initialized(mpd_decoder, audio_format,
- is.IsSeekable(),
- mpc_streaminfo_get_length(&info));
-
- DecoderCommand cmd = DecoderCommand::NONE;
- do {
- if (cmd == DecoderCommand::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) {
- LogWarning(mpcdec_domain,
- "Failed to decode sample");
- break;
- }
-
- if (frame.bits == -1)
- break;
-
- mpc_uint32_t ret = frame.samples;
- ret *= info.channels;
-
- int32_t chunk[ARRAY_SIZE(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 != DecoderCommand::STOP);
-
- mpc_demux_exit(demux);
-}
-
-static float
-mpcdec_get_file_duration(InputStream &is)
-{
- mpc_decoder_data data(is, 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(InputStream &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 DecoderPlugin 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
deleted file mode 100644
index 23ecc801e..000000000
--- a/src/decoder/MpcdecDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_MPCDEC_HXX
-#define MPD_DECODER_MPCDEC_HXX
-
-extern const struct DecoderPlugin mpcdec_decoder_plugin;
-
-#endif
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx
deleted file mode 100644
index df23f7847..000000000
--- a/src/decoder/Mpg123DecoderPlugin.cxx
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "Mpg123DecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <mpg123.h>
-#include <stdio.h>
-
-static constexpr Domain mpg123_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)
-{
- int error;
- int channels, encoding;
- long rate;
-
- /* mpg123_open() wants a writable string :-( */
- char *const path2 = const_cast<char *>(path_fs);
-
- error = mpg123_open(handle, path2);
- if (error != MPG123_OK) {
- FormatWarning(mpg123_domain,
- "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) {
- FormatWarning(mpg123_domain,
- "mpg123_getformat() failed: %s",
- mpg123_plain_strerror(error));
- return false;
- }
-
- if (encoding != MPG123_ENC_SIGNED_16) {
- /* other formats not yet implemented */
- FormatWarning(mpg123_domain,
- "expected MPG123_ENC_SIGNED_16, got %d",
- encoding);
- return false;
- }
-
- Error error2;
- if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16,
- channels, error2)) {
- LogError(error2);
- return false;
- }
-
- return true;
-}
-
-static void
-mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
-{
- mpg123_handle *handle;
- int error;
- off_t num_samples;
- struct mpg123_frameinfo info;
-
- /* open the file */
-
- handle = mpg123_new(nullptr, &error);
- if (handle == nullptr) {
- FormatError(mpg123_domain,
- "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 */
-
- DecoderCommand cmd;
- 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)
- FormatWarning(mpg123_domain,
- "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 == DecoderCommand::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 = DecoderCommand::NONE;
- }
- } while (cmd == DecoderCommand::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) {
- FormatError(mpg123_domain,
- "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 DecoderPlugin 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
deleted file mode 100644
index 10f7c37f5..000000000
--- a/src/decoder/Mpg123DecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_MPG123_HXX
-#define MPD_DECODER_MPG123_HXX
-
-extern const struct DecoderPlugin mpg123_decoder_plugin;
-
-#endif
diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx
deleted file mode 100644
index 565dbafcf..000000000
--- a/src/decoder/OggCodec.cxx
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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(Decoder *decoder, InputStream &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
deleted file mode 100644
index 857871607..000000000
--- a/src/decoder/OggCodec.hxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * 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(Decoder *decoder, InputStream &is);
-
-#endif /* _OGG_COMMON_H */
diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx
deleted file mode 100644
index 65c7fa3ce..000000000
--- a/src/decoder/OggFind.cxx
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OggFind.hxx"
-#include "OggSyncState.hxx"
-#include "util/Error.hxx"
-
-#include <stdio.h>
-
-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;
- }
-}
-
-bool
-OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
- InputStream::offset_type offset, int whence)
-{
- oy.Reset();
-
- /* reset the stream to clear any previous partial packet
- data */
- ogg_stream_reset(&os);
-
- return is.LockSeek(offset, whence, IgnoreError()) &&
- oy.ExpectPageSeekIn(os);
-}
-
-bool
-OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
- InputStream &is)
-{
- if (is.size > 0 && is.size - is.offset < 65536)
- return OggFindEOS(oy, os, packet);
-
- if (!is.CheapSeeking())
- return false;
-
- return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) &&
- OggFindEOS(oy, os, packet);
-}
diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx
deleted file mode 100644
index ad51ccdf3..000000000
--- a/src/decoder/OggFind.hxx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OGG_FIND_HXX
-#define MPD_OGG_FIND_HXX
-
-#include "check.h"
-#include "InputStream.hxx"
-
-#include <ogg/ogg.h>
-
-struct InputStream;
-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);
-
-/**
- * Seek the #InputStream and find the next Ogg page.
- */
-bool
-OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
- InputStream::offset_type offset, int whence);
-
-/**
- * Try to find the end-of-stream (EOS) packet. Seek to the end of the
- * file if necessary.
- *
- * @return true if the EOS packet was found
- */
-bool
-OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
- InputStream &is);
-
-#endif
diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx
deleted file mode 100644
index 5235c1bd8..000000000
--- a/src/decoder/OggSyncState.hxx
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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;
-
- InputStream &is;
- Decoder *const decoder;
-
-public:
- OggSyncState(InputStream &_is, 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
deleted file mode 100644
index 8f181ce57..000000000
--- a/src/decoder/OggUtil.cxx
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OggUtil.hxx"
-#include "DecoderAPI.hxx"
-
-bool
-OggFeed(ogg_sync_state &oy, Decoder *decoder,
- InputStream &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, InputStream &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, InputStream &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, InputStream &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, InputStream &input_stream)
-{
- size_t remaining_skipped = 32768;
-
- 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, InputStream &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
deleted file mode 100644
index 41fc755ba..000000000
--- a/src/decoder/OggUtil.hxx
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OGG_UTIL_HXX
-#define MPD_OGG_UTIL_HXX
-
-#include "check.h"
-
-#include <ogg/ogg.h>
-
-#include <stddef.h>
-
-struct InputStream;
-struct Decoder;
-
-/**
- * Feed data from the #InputStream into the #ogg_sync_state.
- *
- * @return false on error or end-of-file
- */
-bool
-OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &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, InputStream &is);
-
-/**
- * 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, InputStream &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, InputStream &is);
-
-/**
- * Like OggExpectPage(), but allow skipping garbage (after seeking).
- */
-bool
-OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
- Decoder *decoder, InputStream &is);
-
-/**
- * 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, InputStream &is);
-
-#endif
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx
deleted file mode 100644
index 01ea3687a..000000000
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "OpusDecoderPlugin.h"
-#include "OpusDomain.hxx"
-#include "OpusHead.hxx"
-#include "OpusTags.hxx"
-#include "OggUtil.hxx"
-#include "OggFind.hxx"
-#include "OggSyncState.hxx"
-#include "DecoderAPI.hxx"
-#include "OggCodec.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/TagBuilder.hxx"
-#include "InputStream.hxx"
-#include "util/Error.hxx"
-#include "Log.hxx"
-
-#include <opus.h>
-#include <ogg/ogg.h>
-
-#include <glib.h>
-
-#include <string.h>
-#include <stdio.h>
-
-static constexpr 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)
-{
- LogDebug(opus_domain, opus_get_version_string());
-
- return true;
-}
-
-class MPDOpusDecoder {
- Decoder &decoder;
- InputStream &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;
-
- ogg_int64_t eos_granulepos;
-
- size_t frame_size;
-
-public:
- MPDOpusDecoder(Decoder &_decoder,
- InputStream &_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);
-
- DecoderCommand HandlePackets();
- DecoderCommand HandlePacket(const ogg_packet &packet);
- DecoderCommand HandleBOS(const ogg_packet &packet);
- DecoderCommand HandleTags(const ogg_packet &packet);
- DecoderCommand HandleAudio(const ogg_packet &packet);
-
- bool Seek(OggSyncState &oy, double where);
-};
-
-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 DecoderCommand
-MPDOpusDecoder::HandlePackets()
-{
- ogg_packet packet;
- while (ogg_stream_packetout(&os, &packet) == 1) {
- auto cmd = HandlePacket(packet);
- if (cmd != DecoderCommand::NONE)
- return cmd;
- }
-
- return DecoderCommand::NONE;
-}
-
-inline DecoderCommand
-MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
-{
- if (packet.e_o_s)
- return DecoderCommand::STOP;
-
- if (packet.b_o_s)
- return HandleBOS(packet);
- else if (!found_opus)
- return DecoderCommand::STOP;
-
- if (IsOpusTags(packet))
- return HandleTags(packet);
-
- return HandleAudio(packet);
-}
-
-/**
- * Load the end-of-stream packet and restore the previous file
- * position.
- */
-static bool
-LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
- ogg_packet &packet)
-{
- if (!is.CheapSeeking())
- /* we do this for local files only, because seeking
- around remote files is expensive and not worth the
- troubl */
- return -1;
-
- const auto old_offset = is.offset;
- if (old_offset < 0)
- return -1;
-
- /* create temporary Ogg objects for seeking and parsing the
- EOS packet */
- OggSyncState oy(is, decoder);
- ogg_stream_state os;
- ogg_stream_init(&os, serialno);
-
- bool result = OggSeekFindEOS(oy, os, packet, is);
- ogg_stream_clear(&os);
-
- /* restore the previous file position */
- is.Seek(old_offset, SEEK_SET, IgnoreError());
-
- return result;
-}
-
-/**
- * Load the end-of-stream granulepos and restore the previous file
- * position.
- *
- * @return -1 on error
- */
-gcc_pure
-static ogg_int64_t
-LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno)
-{
- ogg_packet packet;
- if (!LoadEOSPacket(is, decoder, serialno, packet))
- return -1;
-
- return packet.granulepos;
-}
-
-inline DecoderCommand
-MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
-{
- assert(packet.b_o_s);
-
- if (found_opus || !IsOpusHead(packet))
- return DecoderCommand::STOP;
-
- unsigned channels;
- if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
- !audio_valid_channel_count(channels))
- return DecoderCommand::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) {
- FormatError(opus_domain, "libopus error: %s",
- opus_strerror(opus_error));
- return DecoderCommand::STOP;
- }
-
- eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
- opus_serialno);
- const double duration = eos_granulepos >= 0
- ? double(eos_granulepos) / opus_sample_rate
- : -1.0;
-
- const AudioFormat audio_format(opus_sample_rate,
- SampleFormat::S16, channels);
- decoder_initialized(decoder, audio_format,
- eos_granulepos > 0, duration);
- 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 DecoderCommand
-MPDOpusDecoder::HandleTags(const ogg_packet &packet)
-{
- ReplayGainInfo rgi;
- rgi.Clear();
-
- TagBuilder tag_builder;
-
- DecoderCommand cmd;
- if (ScanOpusTags(packet.packet, packet.bytes,
- &rgi,
- &add_tag_handler, &tag_builder) &&
- !tag_builder.IsEmpty()) {
- decoder_replay_gain(decoder, &rgi);
-
- Tag tag;
- tag_builder.Commit(tag);
- cmd = decoder_tag(decoder, input_stream, std::move(tag));
- } else
- cmd = decoder_get_command(decoder);
-
- return cmd;
-}
-
-inline DecoderCommand
-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) {
- LogError(opus_domain, opus_strerror(nframes));
- return DecoderCommand::STOP;
- }
-
- if (nframes > 0) {
- const size_t nbytes = nframes * frame_size;
- auto cmd = decoder_data(decoder, input_stream,
- output_buffer, nbytes,
- 0);
- if (cmd != DecoderCommand::NONE)
- return cmd;
-
- if (packet.granulepos > 0)
- decoder_timestamp(decoder,
- double(packet.granulepos)
- / opus_sample_rate);
- }
-
- return DecoderCommand::NONE;
-}
-
-bool
-MPDOpusDecoder::Seek(OggSyncState &oy, double where_s)
-{
- assert(eos_granulepos > 0);
- assert(input_stream.seekable);
- assert(input_stream.size > 0);
- assert(input_stream.offset >= 0);
-
- const ogg_int64_t where_granulepos(where_s * opus_sample_rate);
-
- /* interpolate the file offset where we expect to find the
- given granule position */
- /* TODO: implement binary search */
- InputStream::offset_type offset(where_granulepos * input_stream.size
- / eos_granulepos);
-
- if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET))
- return false;
-
- decoder_timestamp(decoder, where_s);
- return true;
-}
-
-static void
-mpd_opus_stream_decode(Decoder &decoder,
- InputStream &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.LockRewind(IgnoreError());
-
- MPDOpusDecoder d(decoder, input_stream);
- OggSyncState oy(input_stream, &decoder);
-
- if (!d.ReadFirstPage(oy))
- return;
-
- while (true) {
- auto cmd = d.HandlePackets();
- if (cmd == DecoderCommand::SEEK) {
- if (d.Seek(oy, decoder_seek_where(decoder)))
- decoder_command_finished(decoder);
- else
- decoder_seek_error(decoder);
-
- continue;
- }
-
- if (cmd != DecoderCommand::NONE)
- break;
-
- if (!d.ReadNextPage(oy))
- break;
- }
-}
-
-static bool
-mpd_opus_scan_stream(InputStream &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,
- nullptr,
- handler, handler_ctx))
- result = false;
-
- break;
- }
- }
-
- if (packet.e_o_s || OggSeekFindEOS(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 DecoderPlugin 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
deleted file mode 100644
index 263ac6e2d..000000000
--- a/src/decoder/OpusDecoderPlugin.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_OPUS_H
-#define MPD_DECODER_OPUS_H
-
-extern const struct DecoderPlugin opus_decoder_plugin;
-
-#endif
diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/OpusDomain.cxx
deleted file mode 100644
index b00e2a553..000000000
--- a/src/decoder/OpusDomain.cxx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OpusDomain.hxx"
-#include "util/Domain.hxx"
-
-const Domain opus_domain("opus");
diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/OpusDomain.hxx
deleted file mode 100644
index 2b56c427c..000000000
--- a/src/decoder/OpusDomain.hxx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OPUS_DOMAIN_HXX
-#define MPD_OPUS_DOMAIN_HXX
-
-#include "check.h"
-
-extern const class Domain opus_domain;
-
-#endif
diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx
deleted file mode 100644
index 0417d3905..000000000
--- a/src/decoder/OpusHead.cxx
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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
deleted file mode 100644
index fa6a2b666..000000000
--- a/src/decoder/OpusHead.hxx
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 2bb39b748..000000000
--- a/src/decoder/OpusReader.hxx
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OPUS_READER_HXX
-#define MPD_OPUS_READER_HXX
-
-#include "check.h"
-
-#include <algorithm>
-
-#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];
- *std::copy_n(src, length, dest) = 0;
- return dest;
- }
-};
-
-#endif
diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx
deleted file mode 100644
index f7729e5ad..000000000
--- a/src/decoder/OpusTags.cxx
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OpusTags.hxx"
-#include "OpusReader.hxx"
-#include "XiphTags.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/Tag.hxx"
-#include "ReplayGainInfo.hxx"
-
-#include <stdint.h>
-#include <string.h>
-#include <stdlib.h>
-
-gcc_pure
-static TagType
-ParseOpusTagName(const char *name)
-{
- TagType type = tag_name_parse_i(name);
- if (type != TAG_NUM_OF_ITEM_TYPES)
- return type;
-
- return tag_table_lookup_i(xiph_tags, name);
-}
-
-static void
-ScanOneOpusTag(const char *name, const char *value,
- ReplayGainInfo *rgi,
- const struct tag_handler *handler, void *ctx)
-{
- if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) {
- /* R128_TRACK_GAIN is a Q7.8 fixed point number in
- dB */
-
- char *endptr;
- long l = strtol(value, &endptr, 10);
- if (endptr > value && *endptr == 0)
- rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.;
- }
-
- tag_handler_invoke_pair(handler, ctx, name, value);
-
- if (handler->tag != nullptr) {
- TagType t = ParseOpusTagName(name);
- if (t != TAG_NUM_OF_ITEM_TYPES)
- tag_handler_invoke_tag(handler, ctx, t, value);
- }
-}
-
-bool
-ScanOpusTags(const void *data, size_t size,
- ReplayGainInfo *rgi,
- 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, rgi, handler, ctx);
- }
-
- delete[] p;
- }
-
- return true;
-}
diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx
deleted file mode 100644
index e1f1a1ff1..000000000
--- a/src/decoder/OpusTags.hxx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OPUS_TAGS_HXX
-#define MPD_OPUS_TAGS_HXX
-
-#include "check.h"
-
-#include <stddef.h>
-
-struct ReplayGainInfo;
-
-bool
-ScanOpusTags(const void *data, size_t size,
- ReplayGainInfo *rgi,
- const struct tag_handler *handler, void *ctx);
-
-#endif
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx
deleted file mode 100644
index dbc38fb76..000000000
--- a/src/decoder/PcmDecoderPlugin.cxx
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "decoder/PcmDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "util/Error.hxx"
-#include "util/ByteReverse.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdio.h> /* for SEEK_SET */
-
-static void
-pcm_stream_decode(Decoder &decoder, InputStream &is)
-{
- static constexpr AudioFormat audio_format = {
- 44100,
- SampleFormat::S16,
- 2,
- };
-
- const char *const mime = is.GetMimeType();
- const bool reverse_endian = mime != nullptr &&
- strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
-
- const double time_to_size = audio_format.GetTimeToSize();
-
- float total_time = -1;
- const auto size = is.GetSize();
- if (size >= 0)
- total_time = size / time_to_size;
-
- decoder_initialized(decoder, audio_format,
- is.IsSeekable(), total_time);
-
- DecoderCommand cmd;
- do {
- char buffer[4096];
-
- size_t nbytes = decoder_read(decoder, is,
- buffer, sizeof(buffer));
-
- if (nbytes == 0 && is.LockIsEOF())
- 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 == DecoderCommand::SEEK) {
- InputStream::offset_type offset(time_to_size *
- decoder_seek_where(decoder));
-
- Error error;
- if (is.LockSeek(offset, SEEK_SET, error)) {
- decoder_command_finished(decoder);
- } else {
- LogError(error);
- decoder_seek_error(decoder);
- }
-
- cmd = DecoderCommand::NONE;
- }
- } while (cmd == DecoderCommand::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 DecoderPlugin 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
deleted file mode 100644
index 38e4a5020..000000000
--- a/src/decoder/PcmDecoderPlugin.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 DecoderPlugin pcm_decoder_plugin;
-
-#endif
diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/SidplayDecoderPlugin.cxx
deleted file mode 100644
index 160337594..000000000
--- a/src/decoder/SidplayDecoderPlugin.cxx
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SidplayDecoderPlugin.hxx"
-#include "../DecoderAPI.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Domain.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <glib.h>
-
-#include <sidplay/sidplay2.h>
-#include <sidplay/builders/resid.h>
-#include <sidplay/utils/SidTuneMod.h>
-
-#define SUBTUNE_PREFIX "tune_"
-
-static constexpr Domain sidplay_domain("sidplay");
-
-static GPatternSpec *path_with_subtune;
-static const char *songlength_file;
-static GKeyFile *songlength_database;
-
-static bool all_files_are_containers;
-static unsigned default_songlength;
-
-static bool filter_setting;
-
-static GKeyFile *
-sidplay_load_songlength_db(const char *path)
-{
- GError *error = nullptr;
- gchar *data;
- gsize size;
-
- if (!g_file_get_contents(path, &data, &size, &error)) {
- FormatError(sidplay_domain,
- "unable to read songlengths file %s: %s",
- path, error->message);
- g_error_free(error);
- return nullptr;
- }
-
- /* replace any ; comment characters with # */
- for (gsize i = 0; i < size; i++)
- if (data[i] == ';')
- data[i] = '#';
-
- GKeyFile *db = g_key_file_new();
- bool success = g_key_file_load_from_data(db, data, size,
- G_KEY_FILE_NONE, &error);
- g_free(data);
- if (!success) {
- FormatError(sidplay_domain,
- "unable to parse songlengths file %s: %s",
- path, error->message);
- g_error_free(error);
- g_key_file_free(db);
- return nullptr;
- }
-
- g_key_file_set_list_separator(db, ' ');
- return db;
-}
-
-static bool
-sidplay_init(const config_param &param)
-{
- /* read the songlengths database file */
- songlength_file = param.GetBlockValue("songlength_database");
- if (songlength_file != nullptr)
- songlength_database = sidplay_load_songlength_db(songlength_file);
-
- default_songlength = param.GetBlockValue("default_songlength", 0u);
-
- all_files_are_containers =
- param.GetBlockValue("all_files_are_containers", true);
-
- path_with_subtune=g_pattern_spec_new(
- "*/" SUBTUNE_PREFIX "???.sid");
-
- filter_setting = param.GetBlockValue("filter", true);
-
- return true;
-}
-
-static void
-sidplay_finish()
-{
- g_pattern_spec_free(path_with_subtune);
-
- if(songlength_database)
- g_key_file_free(songlength_database);
-}
-
-/**
- * returns the file path stripped of any /tune_xxx.sid subtune
- * suffix
- */
-static char *
-get_container_name(const char *path_fs)
-{
- char *path_container=g_strdup(path_fs);
-
- if(!g_pattern_match(path_with_subtune,
- strlen(path_container), path_container, nullptr))
- return path_container;
-
- char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
- if(ptr) *ptr='\0';
-
- return path_container;
-}
-
-/**
- * returns tune number from file.sid/tune_xxx.sid style path or 1 if
- * no subtune is appended
- */
-static unsigned
-get_song_num(const char *path_fs)
-{
- if(g_pattern_match(path_with_subtune,
- strlen(path_fs), path_fs, nullptr)) {
- char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
- if(!sub) return 1;
-
- sub+=strlen("/" SUBTUNE_PREFIX);
- int song_num=strtol(sub, nullptr, 10);
-
- if (errno == EINVAL)
- return 1;
- else
- return song_num;
- } else
- return 1;
-}
-
-/* get the song length in seconds */
-static int
-get_song_length(const char *path_fs)
-{
- if (songlength_database == nullptr)
- return -1;
-
- gchar *sid_file=get_container_name(path_fs);
- SidTuneMod tune(sid_file);
- g_free(sid_file);
- if(!tune) {
- LogWarning(sidplay_domain,
- "failed to load file for calculating md5 sum");
- return -1;
- }
- char md5sum[SIDTUNE_MD5_LENGTH+1];
- tune.createMD5(md5sum);
-
- const unsigned song_num = get_song_num(path_fs);
-
- gsize num_items;
- gchar **values=g_key_file_get_string_list(songlength_database,
- "Database", md5sum, &num_items, nullptr);
- if(!values || song_num>num_items) {
- g_strfreev(values);
- return -1;
- }
-
- int minutes=strtol(values[song_num-1], nullptr, 10);
- if(errno==EINVAL) minutes=0;
-
- int seconds;
- char *ptr=strchr(values[song_num-1], ':');
- if(ptr) {
- seconds=strtol(ptr+1, nullptr, 10);
- if(errno==EINVAL) seconds=0;
- } else
- seconds=0;
-
- g_strfreev(values);
-
- return (minutes*60)+seconds;
-}
-
-static void
-sidplay_file_decode(Decoder &decoder, const char *path_fs)
-{
- int channels;
-
- /* load the tune */
-
- char *path_container=get_container_name(path_fs);
- SidTune tune(path_container, nullptr, true);
- g_free(path_container);
- if (!tune) {
- LogWarning(sidplay_domain, "failed to load file");
- return;
- }
-
- int song_num=get_song_num(path_fs);
- tune.selectSong(song_num);
-
- int song_len=get_song_length(path_fs);
- if(song_len==-1) song_len=default_songlength;
-
- /* initialize the player */
-
- sidplay2 player;
- int iret = player.load(&tune);
- if (iret != 0) {
- FormatWarning(sidplay_domain,
- "sidplay2.load() failed: %s", player.error());
- return;
- }
-
- /* initialize the builder */
-
- ReSIDBuilder builder("ReSID");
- if (!builder) {
- LogWarning(sidplay_domain,
- "failed to initialize ReSIDBuilder");
- return;
- }
-
- builder.create(player.info().maxsids);
- if (!builder) {
- LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
- return;
- }
-
- builder.filter(filter_setting);
- if (!builder) {
- LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
- return;
- }
-
- /* configure the player */
-
- sid2_config_t config = player.config();
-
- config.clockDefault = SID2_CLOCK_PAL;
- config.clockForced = true;
- config.clockSpeed = SID2_CLOCK_CORRECT;
- config.frequency = 48000;
- config.optimisation = SID2_DEFAULT_OPTIMISATION;
-
- config.precision = 16;
- config.sidDefault = SID2_MOS6581;
- config.sidEmulation = &builder;
- config.sidModel = SID2_MODEL_CORRECT;
- config.sidSamples = true;
- config.sampleFormat = IsLittleEndian()
- ? SID2_LITTLE_SIGNED
- : SID2_BIG_SIGNED;
- if (tune.isStereo()) {
- config.playback = sid2_stereo;
- channels = 2;
- } else {
- config.playback = sid2_mono;
- channels = 1;
- }
-
- iret = player.config(config);
- if (iret != 0) {
- FormatWarning(sidplay_domain,
- "sidplay2.config() failed: %s", player.error());
- return;
- }
-
- /* initialize the MPD decoder */
-
- const AudioFormat audio_format(48000, SampleFormat::S16, channels);
- assert(audio_format.IsValid());
-
- decoder_initialized(decoder, audio_format, true, (float)song_len);
-
- /* .. and play */
-
- const unsigned timebase = player.timebase();
- song_len *= timebase;
-
- DecoderCommand cmd;
- do {
- char buffer[4096];
- size_t nbytes;
-
- nbytes = player.play(buffer, sizeof(buffer));
- if (nbytes == 0)
- break;
-
- decoder_timestamp(decoder, (double)player.time() / timebase);
-
- cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0);
-
- if (cmd == DecoderCommand::SEEK) {
- unsigned data_time = player.time();
- unsigned target_time = (unsigned)
- (decoder_seek_where(decoder) * timebase);
-
- /* can't rewind so return to zero and seek forward */
- if(target_time<data_time) {
- player.stop();
- data_time=0;
- }
-
- /* ignore data until target time is reached */
- while(data_time<target_time) {
- nbytes=player.play(buffer, sizeof(buffer));
- if(nbytes==0)
- break;
- data_time = player.time();
- }
-
- decoder_command_finished(decoder);
- }
-
- if (song_len > 0 && player.time() >= (unsigned)song_len)
- break;
-
- } while (cmd != DecoderCommand::STOP);
-}
-
-static bool
-sidplay_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
-{
- int song_num=get_song_num(path_fs);
- char *path_container=get_container_name(path_fs);
-
- SidTune tune(path_container, nullptr, true);
- g_free(path_container);
- if (!tune)
- return false;
-
- const SidTuneInfo &info = tune.getInfo();
-
- /* title */
- const char *title;
- if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr)
- title=info.infoString[0];
- else
- title="";
-
- if(info.songs>1) {
- char tag_title[1024];
- snprintf(tag_title, sizeof(tag_title),
- "%s (%d/%d)",
- title, song_num, info.songs);
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, tag_title);
- } else
- tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
-
- /* artist */
- if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr)
- tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
- info.infoString[1]);
-
- /* track */
- char track[16];
- sprintf(track, "%d", song_num);
- tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
-
- /* time */
- int song_len=get_song_length(path_fs);
- if (song_len >= 0)
- tag_handler_invoke_duration(handler, handler_ctx, song_len);
-
- return true;
-}
-
-static char *
-sidplay_container_scan(const char *path_fs, const unsigned int tnum)
-{
- SidTune tune(path_fs, nullptr, true);
- if (!tune)
- return nullptr;
-
- const SidTuneInfo &info=tune.getInfo();
-
- /* Don't treat sids containing a single tune
- as containers */
- if(!all_files_are_containers && info.songs<2)
- return nullptr;
-
- /* Construct container/tune path names, eg.
- Delta.sid/tune_001.sid */
- if(tnum<=info.songs) {
- char *subtune= g_strdup_printf(
- SUBTUNE_PREFIX "%03u.sid", tnum);
- return subtune;
- } else
- return nullptr;
-}
-
-static const char *const sidplay_suffixes[] = {
- "sid",
- "mus",
- "str",
- "prg",
- "P00",
- nullptr
-};
-
-extern const struct DecoderPlugin sidplay_decoder_plugin;
-const struct DecoderPlugin sidplay_decoder_plugin = {
- "sidplay",
- sidplay_init,
- sidplay_finish,
- nullptr, /* stream_decode() */
- sidplay_file_decode,
- sidplay_scan_file,
- nullptr, /* stream_tag() */
- sidplay_container_scan,
- sidplay_suffixes,
- nullptr, /* mime_types */
-};
diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/SidplayDecoderPlugin.hxx
deleted file mode 100644
index 16544801f..000000000
--- a/src/decoder/SidplayDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_SIDPLAY_HXX
-#define MPD_DECODER_SIDPLAY_HXX
-
-extern const struct DecoderPlugin sidplay_decoder_plugin;
-
-#endif
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx
deleted file mode 100644
index bcdf6d7ca..000000000
--- a/src/decoder/SndfileDecoderPlugin.cxx
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SndfileDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <sndfile.h>
-
-static constexpr Domain sndfile_domain("sndfile");
-
-struct SndfileInputStream {
- Decoder *const decoder;
- InputStream &is;
-
- size_t Read(void *buffer, size_t size) {
- /* libsndfile chokes on partial reads; therefore
- always force full reads */
- return decoder_read_full(decoder, is, buffer, size)
- ? size
- : 0;
- }
-};
-
-static sf_count_t
-sndfile_vio_get_filelen(void *user_data)
-{
- SndfileInputStream &sis = *(SndfileInputStream *)user_data;
- const InputStream &is = sis.is;
-
- return is.GetSize();
-}
-
-static sf_count_t
-sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
-{
- SndfileInputStream &sis = *(SndfileInputStream *)user_data;
- InputStream &is = sis.is;
-
- Error error;
- if (!is.LockSeek(offset, whence, error)) {
- LogError(error, "Seek failed");
- return -1;
- }
-
- return is.GetOffset();
-}
-
-static sf_count_t
-sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
-{
- SndfileInputStream &sis = *(SndfileInputStream *)user_data;
-
- return sis.Read(ptr, count);
-}
-
-static sf_count_t
-sndfile_vio_write(gcc_unused const void *ptr,
- gcc_unused sf_count_t count,
- gcc_unused void *user_data)
-{
- /* no writing! */
- return -1;
-}
-
-static sf_count_t
-sndfile_vio_tell(void *user_data)
-{
- SndfileInputStream &sis = *(SndfileInputStream *)user_data;
- const InputStream &is = sis.is;
-
- return is.GetOffset();
-}
-
-/**
- * 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(Decoder &decoder, InputStream &is)
-{
- SNDFILE *sf;
- SF_INFO info;
- size_t frame_size;
- sf_count_t read_frames, num_frames;
- int buffer[4096];
-
- info.format = 0;
-
- SndfileInputStream sis{&decoder, is};
- sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
- if (sf == nullptr) {
- LogWarning(sndfile_domain, "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. */
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, info.samplerate,
- SampleFormat::S32,
- info.channels, error)) {
- LogError(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;
-
- DecoderCommand cmd;
- 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 == DecoderCommand::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 = DecoderCommand::NONE;
- }
- } while (cmd == DecoderCommand::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);
- FormatWarning(sndfile_domain,
- "Invalid sample rate in %s", 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 DecoderPlugin 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
deleted file mode 100644
index f8aa65680..000000000
--- a/src/decoder/SndfileDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_SNDFILE_HXX
-#define MPD_DECODER_SNDFILE_HXX
-
-extern const struct DecoderPlugin sndfile_decoder_plugin;
-
-#endif
diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx
deleted file mode 100644
index d4f019b58..000000000
--- a/src/decoder/VorbisComments.cxx
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "VorbisComments.hxx"
-#include "XiphTags.hxx"
-#include "tag/Tag.hxx"
-#include "tag/TagTable.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/TagBuilder.hxx"
-#include "ReplayGainInfo.hxx"
-#include "util/ASCII.hxx"
-
-#include <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 (StringEqualsCaseASCII(comment, needle, len) &&
- comment[len] == '=')
- return comment + len + 1;
-
- return nullptr;
-}
-
-bool
-vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments)
-{
- rgi.Clear();
-
- const char *temp;
- bool found = false;
-
- 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, TagType tag_type,
- const struct tag_handler *handler, void *handler_ctx)
-{
- const char *value;
-
- value = vorbis_comment_value(comment, name);
- if (value != nullptr) {
- 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 != nullptr) {
- char *name = g_strdup((const char*)comment);
- 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 (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], TagType(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)
-{
- TagBuilder tag_builder;
- vorbis_comments_scan(comments, &add_tag_handler, &tag_builder);
- return tag_builder.IsEmpty()
- ? nullptr
- : tag_builder.Commit();
-}
diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx
deleted file mode 100644
index e5a48ef6b..000000000
--- a/src/decoder/VorbisComments.hxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_VORBIS_COMMENTS_HXX
-#define MPD_VORBIS_COMMENTS_HXX
-
-#include "check.h"
-
-struct ReplayGainInfo;
-struct tag_handler;
-struct Tag;
-
-bool
-vorbis_comments_to_replay_gain(ReplayGainInfo &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
deleted file mode 100644
index 4d3e48528..000000000
--- a/src/decoder/VorbisDecoderPlugin.cxx
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "VorbisDecoderPlugin.h"
-#include "VorbisComments.hxx"
-#include "VorbisDomain.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "OggCodec.hxx"
-#include "util/Error.hxx"
-#include "util/UriUtil.hxx"
-#include "util/Macros.hxx"
-#include "system/ByteOrder.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "Log.hxx"
-
-#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 <assert.h>
-#include <errno.h>
-
-struct vorbis_input_stream {
- Decoder *decoder;
-
- InputStream *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;
-
- Error error;
- return vis->seekable &&
- (vis->decoder == nullptr ||
- decoder_get_command(*vis->decoder) != DecoderCommand::STOP) &&
- vis->input_stream->LockSeek(offset, whence, error)
- ? 0 : -1;
-}
-
-/* TODO: check Ogg libraries API and see if we can just not have this func */
-static int ogg_close_cb(gcc_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,
- Decoder *decoder, InputStream &input_stream)
-{
- vis->decoder = decoder;
- vis->input_stream = &input_stream;
- vis->seekable = input_stream.CheapSeeking();
-
- int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks);
- if (ret < 0) {
- if (decoder == nullptr ||
- decoder_get_command(*decoder) == DecoderCommand::NONE)
- FormatWarning(vorbis_domain,
- "Failed to open Ogg Vorbis stream: %s",
- vorbis_strerror(ret));
- return false;
- }
-
- return true;
-}
-
-static void
-vorbis_send_comments(Decoder &decoder, InputStream &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(Decoder &decoder,
- InputStream &input_stream)
-{
- if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS)
- return;
-
- /* rewind the stream, because ogg_codec_detect() has
- moved it */
- input_stream.LockRewind(IgnoreError());
-
- 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 == nullptr) {
- LogWarning(vorbis_domain, "ov_info() has failed");
- return;
- }
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format, vi->rate,
-#ifdef HAVE_TREMOR
- SampleFormat::S16,
-#else
- SampleFormat::FLOAT,
-#endif
- vi->channels, error)) {
- LogError(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);
-
-#ifdef HAVE_TREMOR
- char buffer[4096];
-#else
- float buffer[2048];
- const int frames_per_buffer =
- ARRAY_SIZE(buffer) / audio_format.channels;
- const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels;
-#endif
-
- int prev_section = -1;
- unsigned kbit_rate = 0;
-
- DecoderCommand cmd = decoder_get_command(decoder);
- do {
- if (cmd == DecoderCommand::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),
- IsBigEndian(), 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 == nullptr) {
- LogWarning(vorbis_domain,
- "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 */
- LogWarning(vorbis_domain,
- "audio format change, stopping here");
- break;
- }
-
- char **comments = ov_comment(&vf, -1)->user_comments;
- vorbis_send_comments(decoder, input_stream, comments);
-
- ReplayGainInfo 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 != DecoderCommand::STOP);
-
- ov_clear(&vf);
-}
-
-static bool
-vorbis_scan_stream(InputStream &is,
- const struct tag_handler *handler, void *handler_ctx)
-{
- struct vorbis_input_stream vis;
- OggVorbis_File vf;
-
- if (!vorbis_is_open(&vis, &vf, nullptr, 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", nullptr
-};
-
-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",
- nullptr
-};
-
-const struct DecoderPlugin 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
deleted file mode 100644
index 54953d83a..000000000
--- a/src/decoder/VorbisDecoderPlugin.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_VORBIS_H
-#define MPD_DECODER_VORBIS_H
-
-extern const struct DecoderPlugin vorbis_decoder_plugin;
-
-#endif
diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/VorbisDomain.cxx
deleted file mode 100644
index 32ff4d6b7..000000000
--- a/src/decoder/VorbisDomain.cxx
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "VorbisDomain.hxx"
-#include "util/Domain.hxx"
-
-const Domain vorbis_domain("vorbis");
diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx
deleted file mode 100644
index a35edd041..000000000
--- a/src/decoder/VorbisDomain.hxx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_VORBIS_DOMAIN_HXX
-#define MPD_VORBIS_DOMAIN_HXX
-
-#include "check.h"
-
-extern const class Domain vorbis_domain;
-
-#endif
diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx
deleted file mode 100644
index 98555c5e8..000000000
--- a/src/decoder/WavpackDecoderPlugin.cxx
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "WavpackDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "CheckAudioFormat.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/ApeTag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "Log.hxx"
-
-#include <wavpack/wavpack.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#define ERRORLEN 80
-
-static constexpr Domain wavpack_domain("wavpack");
-
-/** 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(gcc_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(Decoder &decoder, WavpackContext *wpc, bool can_seek)
-{
- bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
- SampleFormat sample_format =
- wavpack_bits_to_sample_format(is_float,
- WavpackGetBytesPerSample(wpc));
-
- Error error;
- AudioFormat audio_format;
- if (!audio_format_init_checked(audio_format,
- WavpackGetSampleRate(wpc),
- sample_format,
- WavpackGetNumChannels(wpc), error)) {
- LogError(error);
- return;
- }
-
- const format_samples_t format_samples = is_float
- ? format_samples_float
- : format_samples_int;
-
- const float total_time = float(WavpackGetNumSamples(wpc))
- / audio_format.sample_rate;
-
- const int bytes_per_sample = WavpackGetBytesPerSample(wpc);
- const int 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 = ARRAY_SIZE(chunk) /
- audio_format.channels;
-
- decoder_initialized(decoder, audio_format, can_seek, total_time);
-
- DecoderCommand cmd = decoder_get_command(decoder);
- while (cmd != DecoderCommand::STOP) {
- if (cmd == DecoderCommand::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, nullptr, 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];
- if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0)
- return false;
-
- *value_r = atof(buffer);
- return true;
-}
-
-static bool
-wavpack_replaygain(ReplayGainInfo &rgi,
- WavpackContext *wpc)
-{
- rgi.Clear();
-
- bool found = false;
- found |= wavpack_tag_float(wpc, "replaygain_track_gain",
- &rgi.tuples[REPLAY_GAIN_TRACK].gain);
- found |= wavpack_tag_float(wpc, "replaygain_track_peak",
- &rgi.tuples[REPLAY_GAIN_TRACK].peak);
- found |= wavpack_tag_float(wpc, "replaygain_album_gain",
- &rgi.tuples[REPLAY_GAIN_ALBUM].gain);
- found |= wavpack_tag_float(wpc, "replaygain_album_peak",
- &rgi.tuples[REPLAY_GAIN_ALBUM].peak);
-
- return found;
-}
-
-static void
-wavpack_scan_tag_item(WavpackContext *wpc, const char *name,
- TagType 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)
-{
- char error[ERRORLEN];
- WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
- if (wpc == nullptr) {
- FormatError(wavpack_domain,
- "failed to open WavPack file \"%s\": %s",
- 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 != nullptr)
- wavpack_scan_tag_item(wpc, name, (TagType)i,
- handler, handler_ctx);
- }
-
- for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i)
- wavpack_scan_tag_item(wpc, i->name, i->type,
- handler, handler_ctx);
-
- if (handler->pair != nullptr) {
- 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 {
- Decoder *decoder;
- InputStream *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 wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1;
-}
-
-static int
-wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
-{
- return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 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, Decoder &decoder,
- InputStream &is)
-{
- isp->decoder = &decoder;
- isp->is = &is;
- isp->last_byte = EOF;
-}
-
-static InputStream *
-wavpack_open_wvc(Decoder &decoder, const char *uri,
- Mutex &mutex, Cond &cond,
- struct wavpack_input *wpi)
-{
- /*
- * As we use dc->utf8url, this function will be bad for
- * single files. utf8url is not absolute file path :/
- */
- if (uri == nullptr)
- return nullptr;
-
- char *wvc_url = g_strconcat(uri, "c", nullptr);
-
- InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond,
- IgnoreError());
- g_free(wvc_url);
-
- if (is_wvc == nullptr)
- return nullptr;
-
- /*
- * And we try to buffer in order to get know
- * about a possible 404 error.
- */
- char first_byte;
- size_t nbytes = decoder_read(decoder, *is_wvc,
- &first_byte, sizeof(first_byte));
- if (nbytes == 0) {
- is_wvc->Close();
- return nullptr;
- }
-
- /* 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(Decoder &decoder, InputStream &is)
-{
- int open_flags = OPEN_NORMALIZE;
- bool can_seek = is.seekable;
-
- wavpack_input isp_wvc;
- InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(),
- is.mutex, is.cond,
- &isp_wvc);
- if (is_wvc != nullptr) {
- open_flags |= OPEN_WVC;
- can_seek &= is_wvc->seekable;
- }
-
- if (!can_seek) {
- open_flags |= OPEN_STREAMING;
- }
-
- wavpack_input isp;
- wavpack_input_init(&isp, decoder, is);
-
- char error[ERRORLEN];
- WavpackContext *wpc =
- WavpackOpenFileInputEx(&mpd_is_reader, &isp,
- open_flags & OPEN_WVC
- ? &isp_wvc : nullptr,
- error, open_flags, 23);
-
- if (wpc == nullptr) {
- FormatError(wavpack_domain,
- "failed to open WavPack stream: %s", error);
- return;
- }
-
- wavpack_decode(decoder, wpc, can_seek);
-
- WavpackCloseFile(wpc);
- if (open_flags & OPEN_WVC) {
- is_wvc->Close();
- }
-}
-
-/*
- * Decodes a file.
- */
-static void
-wavpack_filedecode(Decoder &decoder, const char *fname)
-{
- char error[ERRORLEN];
- WavpackContext *wpc = WavpackOpenFileInput(fname, error,
- OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE,
- 23);
- if (wpc == nullptr) {
- FormatWarning(wavpack_domain,
- "failed to open WavPack file \"%s\": %s",
- fname, error);
- return;
- }
-
- ReplayGainInfo rgi;
- if (wavpack_replaygain(rgi, wpc))
- decoder_replay_gain(decoder, &rgi);
-
- wavpack_decode(decoder, wpc, true);
-
- WavpackCloseFile(wpc);
-}
-
-static char const *const wavpack_suffixes[] = {
- "wv",
- nullptr
-};
-
-static char const *const wavpack_mime_types[] = {
- "audio/x-wavpack",
- nullptr
-};
-
-const struct DecoderPlugin 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
deleted file mode 100644
index 3a2d94532..000000000
--- a/src/decoder/WavpackDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_WAVPACK_HXX
-#define MPD_DECODER_WAVPACK_HXX
-
-extern const struct DecoderPlugin wavpack_decoder_plugin;
-
-#endif
diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx
deleted file mode 100644
index 3da3f1387..000000000
--- a/src/decoder/WildmidiDecoderPlugin.cxx
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "WildmidiDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "tag/TagHandler.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-extern "C" {
-#include <wildmidi_lib.h>
-}
-
-static constexpr Domain wildmidi_domain("wildmidi");
-
-static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000;
-
-static bool
-wildmidi_init(const config_param &param)
-{
- Error error;
- const AllocatedPath path =
- param.GetBlockPath("config_file",
- "/etc/timidity/timidity.cfg",
- error);
- if (path.IsNull())
- FatalError(error);
-
- if (!FileExists(path)) {
- const auto utf8 = path.ToUTF8();
- FormatDebug(wildmidi_domain,
- "configuration file does not exist: %s",
- utf8.c_str());
- return false;
- }
-
- return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0;
-}
-
-static void
-wildmidi_finish(void)
-{
- WildMidi_Shutdown();
-}
-
-static void
-wildmidi_file_decode(Decoder &decoder, const char *path_fs)
-{
- static constexpr AudioFormat audio_format = {
- WILDMIDI_SAMPLE_RATE,
- SampleFormat::S16,
- 2,
- };
- midi *wm;
- const struct _WM_Info *info;
-
- 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);
-
- DecoderCommand cmd;
- 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 == DecoderCommand::SEEK) {
- unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
- decoder_seek_where(decoder);
-
- WildMidi_FastSeek(wm, &seek_where);
- decoder_command_finished(decoder);
- cmd = DecoderCommand::NONE;
- }
-
- } while (cmd == DecoderCommand::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 DecoderPlugin 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
deleted file mode 100644
index a6289612e..000000000
--- a/src/decoder/WildmidiDecoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_WILDMIDI_HXX
-#define MPD_DECODER_WILDMIDI_HXX
-
-extern const struct DecoderPlugin wildmidi_decoder_plugin;
-
-#endif
diff --git a/src/decoder/XiphTags.cxx b/src/decoder/XiphTags.cxx
deleted file mode 100644
index b9958a19a..000000000
--- a/src/decoder/XiphTags.cxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "XiphTags.hxx"
-
-const struct tag_table xiph_tags[] = {
- { "tracknumber", TAG_TRACK },
- { "discnumber", TAG_DISC },
- { "album artist", TAG_ALBUM_ARTIST },
- { nullptr, TAG_NUM_OF_ITEM_TYPES }
-};
diff --git a/src/decoder/XiphTags.hxx b/src/decoder/XiphTags.hxx
deleted file mode 100644
index 606dfef10..000000000
--- a/src/decoder/XiphTags.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_XIPH_TAGS_HXX
-#define MPD_XIPH_TAGS_HXX
-
-#include "check.h"
-#include "tag/TagTable.hxx"
-
-extern const struct tag_table xiph_tags[];
-
-#endif
diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx
new file mode 100644
index 000000000..9cc37ade4
--- /dev/null
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AdPlugDecoderPlugin.h"
+#include "tag/TagHandler.hxx"
+#include "../DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "Log.hxx"
+
+#include <adplug/adplug.h>
+#include <adplug/emuopl.h>
+
+#include <assert.h>
+
+static constexpr Domain adplug_domain("adplug");
+
+static unsigned sample_rate;
+
+static bool
+adplug_init(const config_param &param)
+{
+ FormatDebug(adplug_domain, "adplug %s",
+ CAdPlug::get_version().c_str());
+
+ Error error;
+
+ sample_rate = param.GetBlockValue("sample_rate", 48000u);
+ if (!audio_check_sample_rate(sample_rate, error)) {
+ LogError(error);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+adplug_file_decode(Decoder &decoder, Path path_fs)
+{
+ CEmuopl opl(sample_rate, true, true);
+ opl.init();
+
+ CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl);
+ if (player == nullptr)
+ return;
+
+ const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format, false,
+ SongTime::FromMS(player->songlength()));
+
+ DecoderCommand cmd;
+
+ do {
+ if (!player->update())
+ break;
+
+ int16_t buffer[2048];
+ constexpr unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2;
+ opl.update(buffer, frames_per_buffer);
+ cmd = decoder_data(decoder, nullptr,
+ buffer, sizeof(buffer),
+ 0);
+ } while (cmd == DecoderCommand::NONE);
+
+ delete player;
+}
+
+static void
+adplug_scan_tag(TagType 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(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ CEmuopl opl(sample_rate, true, true);
+ opl.init();
+
+ CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl);
+ if (player == nullptr)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime::FromMS(player->songlength()));
+
+ 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 DecoderPlugin adplug_decoder_plugin = {
+ "adplug",
+ adplug_init,
+ nullptr,
+ nullptr,
+ adplug_file_decode,
+ adplug_scan_file,
+ nullptr,
+ nullptr,
+ adplug_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h
new file mode 100644
index 000000000..539dbbf0a
--- /dev/null
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_ADPLUG_H
+#define MPD_DECODER_ADPLUG_H
+
+extern const struct DecoderPlugin adplug_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
new file mode 100644
index 000000000..a0ef71e49
--- /dev/null
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AudiofileDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <audiofile.h>
+#include <af_vfs.h>
+
+#include <assert.h>
+#include <stdio.h>
+
+static constexpr Domain audiofile_domain("audiofile");
+
+static void
+audiofile_error_func(long, const char *msg)
+{
+ LogWarning(audiofile_domain, msg);
+}
+
+static bool
+audiofile_init(const config_param &)
+{
+ afSetErrorHandler(audiofile_error_func);
+ return true;
+}
+
+struct AudioFileInputStream {
+ Decoder *const decoder;
+ InputStream &is;
+
+ size_t Read(void *buffer, size_t size) {
+ /* libaudiofile does not like partial reads at all,
+ and will abort playback; therefore always force full
+ reads */
+ return decoder_read_full(decoder, is, buffer, size)
+ ? size
+ : 0;
+ }
+};
+
+gcc_pure
+static SongTime
+audiofile_get_duration(AFfilehandle fh)
+{
+ return SongTime::FromScale<uint64_t>(afGetFrameCount(fh, AF_DEFAULT_TRACK),
+ afGetRate(fh, AF_DEFAULT_TRACK));
+}
+
+static ssize_t
+audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
+{
+ AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+
+ return afis.Read(data, length);
+}
+
+static AFfileoffset
+audiofile_file_length(AFvirtualfile *vfile)
+{
+ AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+ InputStream &is = afis.is;
+
+ return is.GetSize();
+}
+
+static AFfileoffset
+audiofile_file_tell(AFvirtualfile *vfile)
+{
+ AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+ InputStream &is = afis.is;
+
+ return is.GetOffset();
+}
+
+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)
+{
+ AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
+ InputStream &is = afis.is;
+
+ offset_type offset = _offset;
+ if (is_relative)
+ offset += is.GetOffset();
+
+ Error error;
+ if (is.LockSeek(offset, error)) {
+ return is.GetOffset();
+ } else {
+ LogError(error, "Seek failed");
+ return -1;
+ }
+}
+
+static AFvirtualfile *
+setup_virtual_fops(AudioFileInputStream &afis)
+{
+ AFvirtualfile *vf = new AFvirtualfile();
+ vf->closure = &afis;
+ 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))) {
+ FormatDebug(audiofile_domain,
+ "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(Decoder &decoder, InputStream &is)
+{
+ if (!is.IsSeekable() || !is.KnownSize()) {
+ LogWarning(audiofile_domain, "not seekable");
+ return;
+ }
+
+ AudioFileInputStream afis{&decoder, is};
+ AFvirtualfile *const vf = setup_virtual_fops(afis);
+
+ const AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr);
+ if (fh == AF_NULL_FILEHANDLE)
+ return;
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format,
+ afGetRate(fh, AF_DEFAULT_TRACK),
+ audiofile_setup_sample_format(fh),
+ afGetVirtualChannels(fh, AF_DEFAULT_TRACK),
+ error)) {
+ LogError(error);
+ afCloseFile(fh);
+ return;
+ }
+
+ const auto total_time = audiofile_get_duration(fh);
+
+ const uint16_t kbit_rate = (uint16_t)
+ (is.GetSize() * uint64_t(8) / total_time.ToMS());
+
+ const unsigned frame_size = (unsigned)
+ afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);
+
+ decoder_initialized(decoder, audio_format, true, total_time);
+
+ DecoderCommand cmd;
+ do {
+ /* pick 1020 since its divisible for 8,16,24, and
+ 32-bit audio */
+ char chunk[1020];
+ const int nframes =
+ afReadFrames(fh, AF_DEFAULT_TRACK, chunk,
+ sizeof(chunk) / frame_size);
+ if (nframes <= 0)
+ break;
+
+ cmd = decoder_data(decoder, nullptr,
+ chunk, nframes * frame_size,
+ kbit_rate);
+
+ if (cmd == DecoderCommand::SEEK) {
+ AFframecount frame = decoder_seek_where_frame(decoder);
+ afSeekFrame(fh, AF_DEFAULT_TRACK, frame);
+
+ decoder_command_finished(decoder);
+ cmd = DecoderCommand::NONE;
+ }
+ } while (cmd == DecoderCommand::NONE);
+
+ afCloseFile(fh);
+}
+
+gcc_pure
+static SignedSongTime
+audiofile_get_duration(InputStream &is)
+{
+ if (!is.IsSeekable() || !is.KnownSize())
+ return SignedSongTime::Negative();
+
+ AudioFileInputStream afis{nullptr, is};
+ AFvirtualfile *vf = setup_virtual_fops(afis);
+ AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr);
+ if (fh == AF_NULL_FILEHANDLE)
+ return SignedSongTime::Negative();
+
+ const auto duration = audiofile_get_duration(fh);
+ afCloseFile(fh);
+ return duration;
+}
+
+static bool
+audiofile_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const auto duration = audiofile_get_duration(is);
+ if (duration.IsNegative())
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration));
+ 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 DecoderPlugin audiofile_decoder_plugin = {
+ "audiofile",
+ audiofile_init,
+ nullptr,
+ audiofile_stream_decode,
+ nullptr,
+ nullptr,
+ audiofile_scan_stream,
+ nullptr,
+ audiofile_suffixes,
+ audiofile_mime_types,
+};
diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx
new file mode 100644
index 000000000..61129076d
--- /dev/null
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_AUDIOFILE_HXX
+#define MPD_DECODER_AUDIOFILE_HXX
+
+extern const struct DecoderPlugin audiofile_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx
new file mode 100644
index 000000000..7321261f6
--- /dev/null
+++ b/src/decoder/plugins/DsdLib.cxx
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* \file
+ *
+ * This file contains functions used by the DSF and DSDIFF decoders.
+ *
+ */
+
+#include "config.h"
+#include "DsdLib.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagId3.hxx"
+#include "util/Error.hxx"
+#include "util/Alloc.hxx"
+
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_ID3TAG
+#include <id3tag.h>
+#endif
+
+bool
+DsdId::Equals(const char *s) const
+{
+ assert(s != nullptr);
+ assert(strlen(s) == sizeof(value));
+
+ return memcmp(value, s, sizeof(value)) == 0;
+}
+
+/**
+ * Skip the #input_stream to the specified offset.
+ */
+bool
+dsdlib_skip_to(Decoder *decoder, InputStream &is,
+ offset_type offset)
+{
+ if (is.IsSeekable())
+ return is.LockSeek(offset, IgnoreError());
+
+ if (is.GetOffset() > offset)
+ return false;
+
+ return dsdlib_skip(decoder, is, offset - is.GetOffset());
+}
+
+/**
+ * Skip some bytes from the #input_stream.
+ */
+bool
+dsdlib_skip(Decoder *decoder, InputStream &is,
+ offset_type delta)
+{
+ if (delta == 0)
+ return true;
+
+ if (is.IsSeekable())
+ return is.LockSeek(is.GetOffset() + delta, IgnoreError());
+
+ if (delta > 1024 * 1024)
+ /* don't skip more than one megabyte; it would be too
+ expensive */
+ return false;
+
+ return decoder_skip(decoder, is, delta);
+}
+
+bool
+dsdlib_valid_freq(uint32_t samplefreq)
+{
+ switch (samplefreq) {
+ case 2822400: /* DSD64, 64xFs, Fs = 44.100kHz */
+ case 3072000: /* DSD64 with Fs = 48.000 kHz */
+ case 5644800:
+ case 6144000:
+ case 11289600:
+ case 12288000:
+ case 22579200:/* DSD512 */
+ case 24576000:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+#ifdef HAVE_ID3TAG
+void
+dsdlib_tag_id3(InputStream &is,
+ const struct tag_handler *handler,
+ void *handler_ctx, int64_t tagoffset)
+{
+ assert(tagoffset >= 0);
+
+ if (tagoffset == 0 || !is.KnownSize())
+ return;
+
+ if (!dsdlib_skip_to(nullptr, is, tagoffset))
+ return;
+
+ /* Prevent broken files causing problems */
+ const auto size = is.GetSize();
+ const auto offset = is.GetOffset();
+ if (offset >= size)
+ return;
+
+ const id3_length_t count = size - offset;
+
+ if (count < 10 || count > 256*1024)
+ return;
+
+ id3_byte_t *const id3_buf = static_cast<id3_byte_t*>(xalloc(count));
+
+ if (!decoder_read_full(nullptr, is, id3_buf, count)) {
+ free(id3_buf);
+ return;
+ }
+
+ struct id3_tag *id3_tag = id3_tag_parse(id3_buf, count);
+ if (id3_tag == nullptr) {
+ free(id3_buf);
+ return;
+ }
+
+ scan_id3_tag(id3_tag, handler, handler_ctx);
+
+ id3_tag_delete(id3_tag);
+
+ free(id3_buf);
+ return;
+}
+#endif
diff --git a/src/decoder/plugins/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx
new file mode 100644
index 000000000..8295bcbf6
--- /dev/null
+++ b/src/decoder/plugins/DsdLib.hxx
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_DSDLIB_HXX
+#define MPD_DECODER_DSDLIB_HXX
+
+#include "system/ByteOrder.hxx"
+#include "input/Offset.hxx"
+#include "Compiler.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct Decoder;
+class InputStream;
+
+struct DsdId {
+ char value[4];
+
+ gcc_pure
+ bool Equals(const char *s) const;
+};
+
+class DsdUint64 {
+ uint32_t lo;
+ uint32_t hi;
+
+public:
+ constexpr uint64_t Read() const {
+ return (uint64_t(FromLE32(hi)) << 32) |
+ uint64_t(FromLE32(lo));
+ }
+};
+
+class DffDsdUint64 {
+ uint32_t hi;
+ uint32_t lo;
+
+public:
+ constexpr uint64_t Read() const {
+ return (uint64_t(FromBE32(hi)) << 32) |
+ uint64_t(FromBE32(lo));
+ }
+};
+
+bool
+dsdlib_skip_to(Decoder *decoder, InputStream &is,
+ offset_type offset);
+
+bool
+dsdlib_skip(Decoder *decoder, InputStream &is,
+ offset_type delta);
+
+/**
+ * Check if the sample frequency is a valid DSD frequency.
+ **/
+gcc_const
+bool
+dsdlib_valid_freq(uint32_t samplefreq);
+
+/**
+ * Add tags from ID3 tag. All tags commonly found in the ID3 tags of
+ * DSF and DSDIFF files are imported
+ */
+void
+dsdlib_tag_id3(InputStream &is,
+ const struct tag_handler *handler,
+ void *handler_ctx, int64_t tagoffset);
+
+#endif
diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
new file mode 100644
index 000000000..b6c79e11e
--- /dev/null
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* \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 "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "util/bit_reverse.h"
+#include "util/Error.hxx"
+#include "system/ByteOrder.hxx"
+#include "tag/TagHandler.hxx"
+#include "DsdLib.hxx"
+#include "Log.hxx"
+
+struct DsdiffHeader {
+ DsdId id;
+ DffDsdUint64 size;
+ DsdId format;
+};
+
+struct DsdiffChunkHeader {
+ DsdId id;
+ DffDsdUint64 size;
+
+ /**
+ * Read the "size" attribute from the specified header, converting it
+ * to the host byte order if needed.
+ */
+ constexpr
+ uint64_t GetSize() const {
+ return size.Read();
+ }
+};
+
+/** struct for DSDIFF native Artist and Title tags */
+struct dsdiff_native_tag {
+ uint32_t size;
+};
+
+struct DsdiffMetaData {
+ unsigned sample_rate, channels;
+ bool bitreverse;
+ offset_type chunk_size;
+};
+
+static bool lsbitfirst;
+
+static bool
+dsdiff_init(const config_param &param)
+{
+ lsbitfirst = param.GetBlockValue("lsbitfirst", false);
+ return true;
+}
+
+static bool
+dsdiff_read_id(Decoder *decoder, InputStream &is,
+ DsdId *id)
+{
+ return decoder_read_full(decoder, is, id, sizeof(*id));
+}
+
+static bool
+dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
+ DsdiffChunkHeader *header)
+{
+ return decoder_read_full(decoder, is, header, sizeof(*header));
+}
+
+static bool
+dsdiff_read_payload(Decoder *decoder, InputStream &is,
+ const DsdiffChunkHeader *header,
+ void *data, size_t length)
+{
+ uint64_t size = header->GetSize();
+ if (size != (uint64_t)length)
+ return false;
+
+ return decoder_read_full(decoder, is, data, length);
+}
+
+/**
+ * Read and parse a "SND" chunk inside "PROP".
+ */
+static bool
+dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
+ DsdiffMetaData *metadata,
+ offset_type end_offset)
+{
+ DsdiffChunkHeader header;
+ while (is.GetOffset() + sizeof(header) <= end_offset) {
+ if (!dsdiff_read_chunk_header(decoder, is, &header))
+ return false;
+
+ offset_type chunk_end_offset = is.GetOffset()
+ + header.GetSize();
+ if (chunk_end_offset > end_offset)
+ return false;
+
+ if (header.id.Equals("FS ")) {
+ uint32_t sample_rate;
+ if (!dsdiff_read_payload(decoder, is, &header,
+ &sample_rate,
+ sizeof(sample_rate)))
+ return false;
+
+ metadata->sample_rate = FromBE32(sample_rate);
+ } else if (header.id.Equals("CHNL")) {
+ uint16_t channels;
+ if (header.GetSize() < sizeof(channels) ||
+ !decoder_read_full(decoder, is,
+ &channels, sizeof(channels)) ||
+ !dsdlib_skip_to(decoder, is, chunk_end_offset))
+ return false;
+
+ metadata->channels = FromBE16(channels);
+ } else if (header.id.Equals("CMPR")) {
+ DsdId type;
+ if (header.GetSize() < sizeof(type) ||
+ !decoder_read_full(decoder, is,
+ &type, sizeof(type)) ||
+ !dsdlib_skip_to(decoder, is, chunk_end_offset))
+ return false;
+
+ if (!type.Equals("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.GetOffset() == end_offset;
+}
+
+/**
+ * Read and parse a "PROP" chunk.
+ */
+static bool
+dsdiff_read_prop(Decoder *decoder, InputStream &is,
+ DsdiffMetaData *metadata,
+ const DsdiffChunkHeader *prop_header)
+{
+ uint64_t prop_size = prop_header->GetSize();
+ const offset_type end_offset = is.GetOffset() + prop_size;
+
+ DsdId prop_id;
+ if (prop_size < sizeof(prop_id) ||
+ !dsdiff_read_id(decoder, is, &prop_id))
+ return false;
+
+ if (prop_id.Equals("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(InputStream &is,
+ const struct tag_handler *handler,
+ void *handler_ctx, offset_type tagoffset,
+ TagType type)
+{
+ if (!dsdlib_skip_to(nullptr, is, tagoffset))
+ return;
+
+ struct dsdiff_native_tag metatag;
+
+ if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
+ return;
+
+ uint32_t length = FromBE32(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 (!decoder_read_full(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(Decoder *decoder, InputStream &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;
+
+ /** offset for artist tag */
+ offset_type artist_offset = 0;
+ /** offset for title tag */
+ offset_type title_offset = 0;
+
+#ifdef HAVE_ID3TAG
+ offset_type id3_offset = 0;
+#endif
+
+ /* Now process all the remaining chunk headers in the stream
+ and record their position and size */
+
+ do {
+ offset_type chunk_size = chunk_header->GetSize();
+
+ /* DIIN chunk, is directly followed by other chunks */
+ if (chunk_header->id.Equals("DIIN"))
+ chunk_size = 0;
+
+ /* DIAR chunk - DSDIFF native tag for Artist */
+ if (chunk_header->id.Equals("DIAR")) {
+ chunk_size = chunk_header->GetSize();
+ artist_offset = is.GetOffset();
+ }
+
+ /* DITI chunk - DSDIFF native tag for Title */
+ if (chunk_header->id.Equals("DITI")) {
+ chunk_size = chunk_header->GetSize();
+ title_offset = is.GetOffset();
+ }
+#ifdef HAVE_ID3TAG
+ /* 'ID3 ' chunk, offspec. Used by sacdextract */
+ if (chunk_header->id.Equals("ID3 ")) {
+ chunk_size = chunk_header->GetSize();
+ id3_offset = is.GetOffset();
+ }
+#endif
+
+ if (!dsdlib_skip(decoder, is, chunk_size))
+ break;
+ } while (dsdiff_read_chunk_header(decoder, is, chunk_header));
+
+ /* done processing chunk headers, process tags if any */
+
+#ifdef HAVE_ID3TAG
+ if (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, id3_offset);
+ return true;
+ }
+#endif
+
+ if (artist_offset != 0)
+ dsdiff_handle_native_tag(is, handler, handler_ctx,
+ artist_offset, TAG_ARTIST);
+
+ if (title_offset != 0)
+ dsdiff_handle_native_tag(is, handler, handler_ctx,
+ title_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(Decoder *decoder, InputStream &is,
+ DsdiffMetaData *metadata,
+ DsdiffChunkHeader *chunk_header)
+{
+ DsdiffHeader header;
+ if (!decoder_read_full(decoder, is, &header, sizeof(header)) ||
+ !header.id.Equals("FRM8") ||
+ !header.format.Equals("DSD "))
+ return false;
+
+ while (true) {
+ if (!dsdiff_read_chunk_header(decoder, is,
+ chunk_header))
+ return false;
+
+ if (chunk_header->id.Equals("PROP")) {
+ if (!dsdiff_read_prop(decoder, is, metadata,
+ chunk_header))
+ return false;
+ } else if (chunk_header->id.Equals("DSD ")) {
+ const offset_type chunk_size = chunk_header->GetSize();
+ metadata->chunk_size = chunk_size;
+ return true;
+ } else {
+ /* ignore unknown chunk */
+ const offset_type chunk_size = chunk_header->GetSize();
+ const offset_type chunk_end_offset =
+ is.GetOffset() + 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);
+}
+
+static offset_type
+FrameToOffset(uint64_t frame, unsigned channels)
+{
+ return frame * channels;
+}
+
+/**
+ * Decode one "DSD" chunk.
+ */
+static bool
+dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
+ unsigned channels, unsigned sample_rate,
+ const offset_type total_bytes)
+{
+ const offset_type start_offset = is.GetOffset();
+
+ 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 size_t buffer_size = buffer_frames * frame_size;
+
+ auto cmd = decoder_get_command(decoder);
+ for (offset_type remaining_bytes = total_bytes;
+ remaining_bytes >= frame_size && cmd != DecoderCommand::STOP;) {
+ if (cmd == DecoderCommand::SEEK) {
+ uint64_t frame = decoder_seek_where_frame(decoder);
+ offset_type offset = FrameToOffset(frame, channels);
+ if (offset >= total_bytes) {
+ decoder_command_finished(decoder);
+ break;
+ }
+
+ if (dsdlib_skip_to(&decoder, is,
+ start_offset + offset)) {
+ decoder_command_finished(decoder);
+ remaining_bytes = total_bytes - offset;
+ } else
+ decoder_seek_error(decoder);
+ }
+
+ /* see how much aligned data from the remaining chunk
+ fits into the local buffer */
+ size_t now_size = buffer_size;
+ if (remaining_bytes < (offset_type)now_size) {
+ unsigned now_frames = remaining_bytes / frame_size;
+ now_size = now_frames * frame_size;
+ }
+
+ if (!decoder_read_full(&decoder, is, buffer, now_size))
+ return false;
+
+ const size_t nbytes = now_size;
+ remaining_bytes -= nbytes;
+
+ if (lsbitfirst)
+ bit_reverse_buffer(buffer, buffer + nbytes);
+
+ cmd = decoder_data(decoder, is, buffer, nbytes,
+ sample_rate / 1000);
+ }
+
+ return true;
+}
+
+static void
+dsdiff_stream_decode(Decoder &decoder, InputStream &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;
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, error)) {
+ LogError(error);
+ return;
+ }
+
+ /* calculate song time from DSD chunk size and sample frequency */
+ offset_type chunk_size = metadata.chunk_size;
+
+ uint64_t n_frames = chunk_size / audio_format.channels;
+ auto songtime = SongTime::FromScale<uint64_t>(n_frames,
+ audio_format.sample_rate);
+
+ /* success: file was recognized */
+ decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime);
+
+ /* every iteration of the following loop decodes one "DSD"
+ chunk from a DFF file */
+
+ dsdiff_decode_chunk(decoder, is,
+ metadata.channels,
+ metadata.sample_rate,
+ chunk_size);
+}
+
+static bool
+dsdiff_scan_stream(InputStream &is,
+ gcc_unused const struct tag_handler *handler,
+ gcc_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, IgnoreError()))
+ /* refuse to parse files which we cannot play anyway */
+ return false;
+
+ /* calculate song time and add as tag */
+ uint64_t n_frames = metadata.chunk_size / audio_format.channels;
+ auto songtime = SongTime::FromScale<uint64_t>(n_frames,
+ audio_format.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 DecoderPlugin 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/plugins/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx
new file mode 100644
index 000000000..7aa36752b
--- /dev/null
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_DSDIFF_H
+#define MPD_DECODER_DSDIFF_H
+
+extern const struct DecoderPlugin dsdiff_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx
new file mode 100644
index 000000000..690616d15
--- /dev/null
+++ b/src/decoder/plugins/DsfDecoderPlugin.cxx
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* \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 "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "util/bit_reverse.h"
+#include "util/Error.hxx"
+#include "system/ByteOrder.hxx"
+#include "DsdLib.hxx"
+#include "tag/TagHandler.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+static constexpr unsigned DSF_BLOCK_SIZE = 4096;
+
+struct DsfMetaData {
+ unsigned sample_rate, channels;
+ bool bitreverse;
+ offset_type n_blocks;
+#ifdef HAVE_ID3TAG
+ offset_type id3_offset;
+#endif
+};
+
+struct DsfHeader {
+ /** DSF header id: "DSD " */
+ DsdId id;
+ /** DSD chunk size, including id = 28 */
+ DsdUint64 size;
+ /** total file size */
+ DsdUint64 fsize;
+ /** pointer to id3v2 metadata, should be at the end of the file */
+ DsdUint64 pmeta;
+};
+
+/** DSF file fmt chunk */
+struct DsfFmtChunk {
+ /** id: "fmt " */
+ DsdId id;
+ /** fmt chunk size, including id, normally 52 */
+ DsdUint64 size;
+ /** 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 */
+ DsdUint64 scnt;
+ /** block size per channel = 4096 */
+ uint32_t block_size;
+ /** reserved, should be all zero */
+ uint32_t reserved;
+};
+
+struct DsfDataChunk {
+ DsdId id;
+ /** "data" chunk size, includes header (id+size) */
+ DsdUint64 size;
+};
+
+/**
+ * Read and parse all needed metadata chunks for DSF files.
+ */
+static bool
+dsf_read_metadata(Decoder *decoder, InputStream &is,
+ DsfMetaData *metadata)
+{
+ DsfHeader dsf_header;
+ if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) ||
+ !dsf_header.id.Equals("DSD "))
+ return false;
+
+ const offset_type chunk_size = dsf_header.size.Read();
+ if (sizeof(dsf_header) != chunk_size)
+ return false;
+
+#ifdef HAVE_ID3TAG
+ const offset_type metadata_offset = dsf_header.pmeta.Read();
+#endif
+
+ /* read the 'fmt ' chunk of the DSF file */
+ DsfFmtChunk dsf_fmt_chunk;
+ if (!decoder_read_full(decoder, is,
+ &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
+ !dsf_fmt_chunk.id.Equals("fmt "))
+ return false;
+
+ const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read();
+ if (fmt_chunk_size != sizeof(dsf_fmt_chunk))
+ return false;
+
+ uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq);
+ const unsigned channels = FromLE32(dsf_fmt_chunk.channelnum);
+
+ /* for now, only support version 1 of the standard, DSD raw stereo
+ files with a sample freq of 2822400 or 5644800 Hz */
+
+ if (FromLE32(dsf_fmt_chunk.version) != 1 ||
+ FromLE32(dsf_fmt_chunk.formatid) != 0 ||
+ !audio_valid_channel_count(channels) ||
+ !dsdlib_valid_freq(samplefreq))
+ return false;
+
+ uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size);
+ /* according to the spec block size should always be 4096 */
+ if (chblksize != DSF_BLOCK_SIZE)
+ return false;
+
+ /* read the 'data' chunk of the DSF file */
+ DsfDataChunk data_chunk;
+ if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) ||
+ !data_chunk.id.Equals("data"))
+ return false;
+
+ /* data size of DSF files are padded to multiple of 4096,
+ we use the actual data size as chunk size */
+
+ offset_type data_size = data_chunk.size.Read();
+ if (data_size < sizeof(data_chunk))
+ return false;
+
+ data_size -= sizeof(data_chunk);
+
+ /* data_size cannot be bigger or equal to total file size */
+ if (is.KnownSize() && data_size > is.GetRest())
+ return false;
+
+ /* use the sample count from the DSF header as the upper
+ bound, because some DSF files contain junk at the end of
+ the "data" chunk */
+ const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read();
+ const offset_type playable_size = samplecnt * channels / 8;
+ if (data_size > playable_size)
+ data_size = playable_size;
+
+ const size_t block_size = channels * DSF_BLOCK_SIZE;
+ metadata->n_blocks = data_size / block_size;
+ metadata->channels = channels;
+ metadata->sample_rate = samplefreq;
+#ifdef HAVE_ID3TAG
+ metadata->id3_offset = metadata_offset;
+#endif
+ /* check bits per sample format, determine if bitreverse is needed */
+ metadata->bitreverse = FromLE32(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);
+}
+
+static void
+InterleaveDsfBlockMono(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src)
+{
+ memcpy(dest, src, DSF_BLOCK_SIZE);
+}
+
+/**
+ * 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
+InterleaveDsfBlockStereo(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src)
+{
+ for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i) {
+ dest[2 * i] = src[i];
+ dest[2 * i + 1] = src[DSF_BLOCK_SIZE + i];
+ }
+}
+
+static void
+InterleaveDsfBlockChannel(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src,
+ unsigned channels)
+{
+ for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i, dest += channels, ++src)
+ *dest = *src;
+}
+
+static void
+InterleaveDsfBlockGeneric(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src,
+ unsigned channels)
+{
+ for (unsigned c = 0; c < channels; ++c, ++dest, src += DSF_BLOCK_SIZE)
+ InterleaveDsfBlockChannel(dest, src, channels);
+}
+
+static void
+InterleaveDsfBlock(uint8_t *gcc_restrict dest, const uint8_t *gcc_restrict src,
+ unsigned channels)
+{
+ if (channels == 1)
+ InterleaveDsfBlockMono(dest, src);
+ else if (channels == 2)
+ InterleaveDsfBlockStereo(dest, src);
+ else
+ InterleaveDsfBlockGeneric(dest, src, channels);
+}
+
+static offset_type
+FrameToBlock(uint64_t frame)
+{
+ return frame / DSF_BLOCK_SIZE;
+}
+
+/**
+ * Decode one complete DSF 'data' chunk i.e. a complete song
+ */
+static bool
+dsf_decode_chunk(Decoder &decoder, InputStream &is,
+ unsigned channels, unsigned sample_rate,
+ offset_type n_blocks,
+ bool bitreverse)
+{
+ const size_t block_size = channels * DSF_BLOCK_SIZE;
+ const offset_type start_offset = is.GetOffset();
+
+ auto cmd = decoder_get_command(decoder);
+ for (offset_type i = 0; i < n_blocks && cmd != DecoderCommand::STOP;) {
+ if (cmd == DecoderCommand::SEEK) {
+ uint64_t frame = decoder_seek_where_frame(decoder);
+ offset_type block = FrameToBlock(frame);
+ if (block >= n_blocks) {
+ decoder_command_finished(decoder);
+ break;
+ }
+
+ offset_type offset =
+ start_offset + block * block_size;
+ if (dsdlib_skip_to(&decoder, is, offset)) {
+ decoder_command_finished(decoder);
+ i = block;
+ } else
+ decoder_seek_error(decoder);
+ }
+
+ /* worst-case buffer size */
+ uint8_t buffer[MAX_CHANNELS * DSF_BLOCK_SIZE];
+ if (!decoder_read_full(&decoder, is, buffer, block_size))
+ return false;
+
+ if (bitreverse)
+ bit_reverse_buffer(buffer, buffer + block_size);
+
+ uint8_t interleaved_buffer[MAX_CHANNELS * DSF_BLOCK_SIZE];
+ InterleaveDsfBlock(interleaved_buffer, buffer, channels);
+
+ cmd = decoder_data(decoder, is,
+ interleaved_buffer, block_size,
+ sample_rate / 1000);
+ ++i;
+ }
+
+ return true;
+}
+
+static void
+dsf_stream_decode(Decoder &decoder, InputStream &is)
+{
+ /* check if it is a proper DSF file */
+ DsfMetaData metadata;
+ if (!dsf_read_metadata(&decoder, is, &metadata))
+ return;
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, error)) {
+ LogError(error);
+ return;
+ }
+ /* Calculate song time from DSD chunk size and sample frequency */
+ const auto n_blocks = metadata.n_blocks;
+ auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE,
+ audio_format.sample_rate);
+
+ /* success: file was recognized */
+ decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime);
+
+ dsf_decode_chunk(decoder, is, metadata.channels,
+ metadata.sample_rate,
+ n_blocks,
+ metadata.bitreverse);
+}
+
+static bool
+dsf_scan_stream(InputStream &is,
+ gcc_unused const struct tag_handler *handler,
+ gcc_unused void *handler_ctx)
+{
+ /* check DSF metadata */
+ DsfMetaData metadata;
+ if (!dsf_read_metadata(nullptr, is, &metadata))
+ return false;
+
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
+ SampleFormat::DSD,
+ metadata.channels, IgnoreError()))
+ /* refuse to parse files which we cannot play anyway */
+ return false;
+
+ /* calculate song time and add as tag */
+ const auto n_blocks = metadata.n_blocks;
+ auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE,
+ audio_format.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",
+ nullptr
+};
+
+static const char *const dsf_mime_types[] = {
+ "application/x-dsf",
+ nullptr
+};
+
+const struct DecoderPlugin 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/plugins/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx
new file mode 100644
index 000000000..02bea0b5c
--- /dev/null
+++ b/src/decoder/plugins/DsfDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_DSF_H
+#define MPD_DECODER_DSF_H
+
+extern const struct DecoderPlugin dsf_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx
new file mode 100644
index 000000000..add23aaa4
--- /dev/null
+++ b/src/decoder/plugins/FaadDecoderPlugin.cxx
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FaadDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "../DecoderBuffer.hxx"
+#include "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <neaacdec.h>
+
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+
+static const unsigned adts_sample_rates[] =
+ { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+ 16000, 12000, 11025, 8000, 7350, 0, 0, 0
+};
+
+static constexpr Domain faad_decoder_domain("faad_decoder");
+
+/**
+ * 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)
+{
+ while (true) {
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(8));
+ if (data.IsNull())
+ /* failed */
+ return 0;
+
+ /* find the 0xff marker */
+ const uint8_t *p = (const uint8_t *)
+ memchr(data.data, 0xff, data.size);
+ if (p == nullptr) {
+ /* no marker - discard the buffer */
+ buffer.Clear();
+ continue;
+ }
+
+ if (p > data.data) {
+ /* discard data before 0xff */
+ buffer.Consume(p - data.data);
+ continue;
+ }
+
+ /* is it a frame? */
+ const size_t frame_length = adts_check_frame(data.data);
+ if (frame_length == 0) {
+ /* it's just some random 0xff byte; discard it
+ and continue searching */
+ buffer.Consume(1);
+ continue;
+ }
+
+ if (buffer.Need(frame_length).IsNull()) {
+ /* not enough data; discard this frame to
+ prevent a possible buffer overflow */
+ buffer.Clear();
+ continue;
+ }
+
+ /* found a full frame! */
+ return frame_length;
+ }
+}
+
+static SignedSongTime
+adts_song_duration(DecoderBuffer &buffer)
+{
+ const InputStream &is = buffer.GetStream();
+ const bool estimate = !is.CheapSeeking();
+ if (estimate && !is.KnownSize())
+ return SignedSongTime::Negative();
+
+ unsigned sample_rate = 0;
+
+ /* Read all frames to ensure correct time and bitrate */
+ unsigned frames = 0;
+ for (;; frames++) {
+ const unsigned frame_length = adts_find_frame(buffer);
+ if (frame_length == 0)
+ break;
+
+ if (frames == 0) {
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read());
+ assert(!data.IsEmpty());
+ assert(frame_length <= data.size);
+
+ sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2];
+ if (sample_rate == 0)
+ break;
+ }
+
+ buffer.Consume(frame_length);
+
+ if (estimate && frames == 128) {
+ /* if this is a remote file, don't slurp the
+ whole file just for checking the song
+ duration; instead, stop after some time and
+ extrapolate the song duration from what we
+ have until now */
+
+ const auto offset = is.GetOffset()
+ - buffer.GetAvailable();
+ if (offset <= 0)
+ return SignedSongTime::Negative();
+
+ const auto file_size = is.GetSize();
+ frames = (frames * file_size) / offset;
+ break;
+ }
+ }
+
+ if (sample_rate == 0)
+ return SignedSongTime::Negative();
+
+ return SignedSongTime::FromScale<uint64_t>(frames * uint64_t(1024),
+ sample_rate);
+}
+
+static SignedSongTime
+faad_song_duration(DecoderBuffer &buffer, InputStream &is)
+{
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5));
+ if (data.IsNull())
+ return SignedSongTime::Negative();
+
+ size_t tagsize = 0;
+ if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) {
+ /* skip the ID3 tag */
+
+ tagsize = (data.data[6] << 21) | (data.data[7] << 14) |
+ (data.data[8] << 7) | (data.data[9] << 0);
+
+ tagsize += 10;
+
+ if (!buffer.Skip(tagsize))
+ return SignedSongTime::Negative();
+
+ data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5));
+ if (data.IsNull())
+ return SignedSongTime::Negative();
+ }
+
+ if (data.size >= 8 && adts_check_frame(data.data) > 0) {
+ /* obtain the duration from the ADTS header */
+
+ if (!is.IsSeekable())
+ return SignedSongTime::Negative();
+
+ auto song_length = adts_song_duration(buffer);
+
+ is.LockSeek(tagsize, IgnoreError());
+
+ buffer.Clear();
+
+ return song_length;
+ } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) {
+ /* obtain the duration from the ADIF header */
+
+ if (!is.KnownSize())
+ return SignedSongTime::Negative();
+
+ size_t skip_size = (data.data[4] & 0x80) ? 9 : 0;
+
+ if (8 + skip_size > data.size)
+ /* not enough data yet; skip parsing this
+ header */
+ return SignedSongTime::Negative();
+
+ unsigned bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) |
+ (data.data[5 + skip_size] << 11) |
+ (data.data[6 + skip_size] << 3) |
+ (data.data[7 + skip_size] & 0xE0);
+
+ const auto size = is.GetSize();
+ if (bit_rate == 0)
+ return SignedSongTime::Negative();
+
+ return SongTime::FromScale(size, bit_rate / 8);
+ } else
+ return SignedSongTime::Negative();
+}
+
+static NeAACDecHandle
+faad_decoder_new()
+{
+ const NeAACDecHandle decoder = NeAACDecOpen();
+
+ NeAACDecConfigurationPtr config =
+ NeAACDecGetCurrentConfiguration(decoder);
+ config->outputFormat = FAAD_FMT_16BIT;
+ config->downMatrix = 1;
+ config->dontUpSampleImplicitSBR = 0;
+ NeAACDecSetConfiguration(decoder, config);
+
+ return decoder;
+}
+
+/**
+ * Wrapper for NeAACDecInit() which works around some API
+ * inconsistencies in libfaad.
+ */
+static bool
+faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer,
+ AudioFormat &audio_format, Error &error)
+{
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read());
+ if (data.IsEmpty()) {
+ error.Set(faad_decoder_domain, "Empty file");
+ return false;
+ }
+
+ uint8_t channels;
+ unsigned long sample_rate;
+ long nbytes = NeAACDecInit(decoder,
+ /* deconst hack, libfaad requires this */
+ const_cast<unsigned char *>(data.data),
+ data.size,
+ &sample_rate, &channels);
+ if (nbytes < 0) {
+ error.Set(faad_decoder_domain, "Not an AAC stream");
+ return false;
+ }
+
+ buffer.Consume(nbytes);
+
+ return audio_format_init_checked(audio_format, sample_rate,
+ SampleFormat::S16, channels, error);
+}
+
+/**
+ * Wrapper for NeAACDecDecode() which works around some API
+ * inconsistencies in libfaad.
+ */
+static const void *
+faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer &buffer,
+ NeAACDecFrameInfo *frame_info)
+{
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read());
+ if (data.IsEmpty())
+ return nullptr;
+
+ return NeAACDecDecode(decoder, frame_info,
+ /* deconst hack, libfaad requires this */
+ const_cast<uint8_t *>(data.data),
+ data.size);
+}
+
+/**
+ * Determine a song file's total playing time.
+ *
+ * The first return value specifies whether the file was recognized.
+ * The second return value is the duration.
+ */
+static std::pair<bool, SignedSongTime>
+faad_get_file_time(InputStream &is)
+{
+ DecoderBuffer buffer(nullptr, is,
+ FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
+ auto duration = faad_song_duration(buffer, is);
+ bool recognized = !duration.IsNegative();
+
+ if (!recognized) {
+ NeAACDecHandle decoder = faad_decoder_new();
+
+ buffer.Fill();
+
+ AudioFormat audio_format;
+ if (faad_decoder_init(decoder, buffer, audio_format,
+ IgnoreError()))
+ recognized = true;
+
+ NeAACDecClose(decoder);
+ }
+
+ return std::make_pair(recognized, duration);
+}
+
+static void
+faad_stream_decode(Decoder &mpd_decoder, InputStream &is,
+ DecoderBuffer &buffer, const NeAACDecHandle decoder)
+{
+ const auto total_time = faad_song_duration(buffer, is);
+
+ if (adts_find_frame(buffer) == 0)
+ return;
+
+ /* initialize it */
+
+ Error error;
+ AudioFormat audio_format;
+ if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
+ LogError(error);
+ return;
+ }
+
+ /* initialize the MPD core */
+
+ decoder_initialized(mpd_decoder, audio_format, false, total_time);
+
+ /* the decoder loop */
+
+ DecoderCommand cmd;
+ unsigned bit_rate = 0;
+ do {
+ /* find the next frame */
+
+ const size_t frame_size = adts_find_frame(buffer);
+ if (frame_size == 0)
+ /* end of file */
+ break;
+
+ /* decode it */
+
+ NeAACDecFrameInfo frame_info;
+ const void *const decoded =
+ faad_decoder_decode(decoder, buffer, &frame_info);
+
+ if (frame_info.error > 0) {
+ FormatWarning(faad_decoder_domain,
+ "error decoding AAC stream: %s",
+ NeAACDecGetErrorMessage(frame_info.error));
+ break;
+ }
+
+ if (frame_info.channels != audio_format.channels) {
+ FormatDefault(faad_decoder_domain,
+ "channel count changed from %u to %u",
+ audio_format.channels, frame_info.channels);
+ break;
+ }
+
+ if (frame_info.samplerate != audio_format.sample_rate) {
+ FormatDefault(faad_decoder_domain,
+ "sample rate changed from %u to %lu",
+ audio_format.sample_rate,
+ (unsigned long)frame_info.samplerate);
+ break;
+ }
+
+ buffer.Consume(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 != DecoderCommand::STOP);
+}
+
+static void
+faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
+{
+ DecoderBuffer buffer(&mpd_decoder, is,
+ FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
+
+ /* create the libfaad decoder */
+
+ const NeAACDecHandle decoder = faad_decoder_new();
+
+ faad_stream_decode(mpd_decoder, is, buffer, decoder);
+
+ /* cleanup */
+
+ NeAACDecClose(decoder);
+}
+
+static bool
+faad_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ auto result = faad_get_file_time(is);
+ if (!result.first)
+ return false;
+
+ if (!result.second.IsNegative())
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime(result.second));
+ return true;
+}
+
+static const char *const faad_suffixes[] = { "aac", nullptr };
+static const char *const faad_mime_types[] = {
+ "audio/aac", "audio/aacp", nullptr
+};
+
+const DecoderPlugin 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/plugins/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx
new file mode 100644
index 000000000..968433e9b
--- /dev/null
+++ b/src/decoder/plugins/FaadDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FAAD_DECODER_PLUGIN_HXX
+#define MPD_FAAD_DECODER_PLUGIN_HXX
+
+extern const struct DecoderPlugin faad_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
new file mode 100644
index 000000000..722f954e2
--- /dev/null
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -0,0 +1,779 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegDecoderPlugin.hxx"
+#include "lib/ffmpeg/Domain.hxx"
+#include "../DecoderAPI.hxx"
+#include "FfmpegMetaData.hxx"
+#include "tag/TagHandler.hxx"
+#include "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "LogV.hxx"
+
+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>
+
+#if LIBAVUTIL_VERSION_MAJOR >= 53
+#include <libavutil/frame.h>
+#endif
+}
+
+#include <assert.h>
+#include <string.h>
+
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
+static LogLevel
+import_ffmpeg_level(int level)
+{
+ if (level <= AV_LOG_FATAL)
+ return LogLevel::ERROR;
+
+ if (level <= AV_LOG_WARNING)
+ return LogLevel::WARNING;
+
+ if (level <= AV_LOG_INFO)
+ return LogLevel::INFO;
+
+ return LogLevel::DEBUG;
+}
+
+static void
+mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level,
+ const char *fmt, va_list vl)
+{
+ const AVClass * cls = nullptr;
+
+ if (ptr != nullptr)
+ cls = *(const AVClass *const*)ptr;
+
+ if (cls != nullptr) {
+ char domain[64];
+ snprintf(domain, sizeof(domain), "%s/%s",
+ ffmpeg_domain.GetName(), cls->item_name(ptr));
+ const Domain d(domain);
+ LogFormatV(d, import_ffmpeg_level(level), fmt, vl);
+ }
+}
+
+struct AvioStream {
+ Decoder *const decoder;
+ InputStream &input;
+
+ AVIOContext *io;
+
+ unsigned char buffer[8192];
+
+ AvioStream(Decoder *_decoder, InputStream &_input)
+ :decoder(_decoder), input(_input), io(nullptr) {}
+
+ ~AvioStream() {
+ if (io != nullptr)
+ av_free(io);
+ }
+
+ bool Open();
+};
+
+static int
+mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
+{
+ AvioStream *stream = (AvioStream *)opaque;
+
+ return decoder_read(stream->decoder, stream->input,
+ (void *)buf, size);
+}
+
+static int64_t
+mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
+{
+ AvioStream *stream = (AvioStream *)opaque;
+
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ pos += stream->input.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!stream->input.KnownSize())
+ return -1;
+
+ pos += stream->input.GetSize();
+ break;
+
+ case AVSEEK_SIZE:
+ if (!stream->input.KnownSize())
+ return -1;
+
+ return stream->input.GetSize();
+
+ default:
+ return -1;
+ }
+
+ if (!stream->input.LockSeek(pos, IgnoreError()))
+ return -1;
+
+ return stream->input.GetOffset();
+}
+
+bool
+AvioStream::Open()
+{
+ io = avio_alloc_context(buffer, sizeof(buffer),
+ false, this,
+ mpd_ffmpeg_stream_read, nullptr,
+ input.IsSeekable()
+ ? mpd_ffmpeg_stream_seek : nullptr);
+ return io != nullptr;
+}
+
+/**
+ * API compatibility wrapper for av_open_input_stream() and
+ * avformat_open_input().
+ */
+static int
+mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
+ AVIOContext *pb,
+ const char *filename,
+ AVInputFormat *fmt)
+{
+ AVFormatContext *context = avformat_alloc_context();
+ if (context == nullptr)
+ return AVERROR(ENOMEM);
+
+ context->pb = pb;
+ *ic_ptr = context;
+ return avformat_open_input(ic_ptr, filename, fmt, nullptr);
+}
+
+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;
+}
+
+gcc_const
+static double
+time_from_ffmpeg(int64_t t, const AVRational time_base)
+{
+ assert(t != (int64_t)AV_NOPTS_VALUE);
+
+ return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
+ / (double)1024;
+}
+
+template<typename Ratio>
+static constexpr AVRational
+RatioToAVRational()
+{
+ return { Ratio::num, Ratio::den };
+}
+
+gcc_const
+static int64_t
+time_to_ffmpeg(SongTime t, const AVRational time_base)
+{
+ return av_rescale_q(t.count(),
+ RatioToAVRational<SongTime::period>(),
+ time_base);
+}
+
+/**
+ * Replace #AV_NOPTS_VALUE with the given fallback.
+ */
+static constexpr int64_t
+timestamp_fallback(int64_t t, int64_t fallback)
+{
+ return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
+ ? t
+ : fallback;
+}
+
+/**
+ * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
+ * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply
+ * assume that the stream's start time is zero, which appears to be
+ * the best way out of that situation.
+ */
+static int64_t
+start_time_fallback(const AVStream &stream)
+{
+ return timestamp_fallback(stream.start_time, 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 **output_buffer,
+ uint8_t **global_buffer, int *global_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 (data_size <= 0)
+ return data_size;
+
+ if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
+ codec_context->channels > 1) {
+ if(*global_buffer_size < data_size) {
+ av_freep(global_buffer);
+
+ *global_buffer = (uint8_t*)av_malloc(data_size);
+
+ if (!*global_buffer)
+ /* Not enough memory - shouldn't happen */
+ return AVERROR(ENOMEM);
+ *global_buffer_size = data_size;
+ }
+ *output_buffer = *global_buffer;
+ copy_interleave_frame2(*output_buffer, frame->extended_data,
+ frame->nb_samples,
+ codec_context->channels,
+ av_get_bytes_per_sample(codec_context->sample_fmt));
+ } else {
+ *output_buffer = frame->extended_data[0];
+ }
+
+ return data_size;
+}
+
+static DecoderCommand
+ffmpeg_send_packet(Decoder &decoder, InputStream &is,
+ const AVPacket *packet,
+ AVCodecContext *codec_context,
+ const AVStream *stream,
+ AVFrame *frame,
+ uint8_t **buffer, int *buffer_size)
+{
+ if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) {
+ auto start = start_time_fallback(*stream);
+ if (packet->pts >= start)
+ decoder_timestamp(decoder,
+ time_from_ffmpeg(packet->pts - start,
+ stream->time_base));
+ }
+
+ AVPacket packet2 = *packet;
+
+ uint8_t *output_buffer;
+
+ DecoderCommand cmd = DecoderCommand::NONE;
+ while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
+ int audio_size = 0;
+ int got_frame = 0;
+ int len = avcodec_decode_audio4(codec_context,
+ frame, &got_frame,
+ &packet2);
+ if (len >= 0 && got_frame) {
+ audio_size = copy_interleave_frame(codec_context,
+ frame,
+ &output_buffer,
+ buffer, buffer_size);
+ if (audio_size < 0)
+ len = audio_size;
+ }
+
+ if (len < 0) {
+ /* if error, we skip the frame */
+ LogDefault(ffmpeg_domain,
+ "decoding failed, frame skipped");
+ break;
+ }
+
+ packet2.data += len;
+ packet2.size -= len;
+
+ if (audio_size <= 0)
+ continue;
+
+ cmd = decoder_data(decoder, is,
+ output_buffer, audio_size,
+ codec_context->bit_rate / 1000);
+ }
+ return cmd;
+}
+
+gcc_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_FLT:
+ 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 != nullptr)
+ FormatError(ffmpeg_domain,
+ "Unsupported libavcodec SampleFormat value: %s (%d)",
+ name, sample_fmt);
+ else
+ FormatError(ffmpeg_domain,
+ "Unsupported libavcodec SampleFormat value: %d",
+ sample_fmt);
+ return SampleFormat::UNDEFINED;
+}
+
+static AVInputFormat *
+ffmpeg_probe(Decoder *decoder, InputStream &is)
+{
+ enum {
+ BUFFER_SIZE = 16384,
+ PADDING = 16,
+ };
+
+ unsigned char buffer[BUFFER_SIZE];
+ size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
+ if (nbytes <= PADDING || !is.LockRewind(IgnoreError()))
+ return nullptr;
+
+ /* 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;
+
+ /* new versions of ffmpeg may add new attributes, and leaving
+ them uninitialized may crash; hopefully, zero-initializing
+ everything we don't know is ok */
+ memset(&avpd, 0, sizeof(avpd));
+
+ avpd.buf = buffer;
+ avpd.buf_size = nbytes;
+ avpd.filename = is.GetURI();
+
+#ifdef AVPROBE_SCORE_MIME
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(56, 5, 1)
+ /* this attribute was added in libav/ffmpeg version 11, but
+ unfortunately it's "uint8_t" instead of "char", and it's
+ not "const" - wtf? */
+ avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType());
+#else
+ /* API problem fixed in FFmpeg 2.5 */
+ avpd.mime_type = is.GetMimeType();
+#endif
+#endif
+
+ return av_probe_input_format(&avpd, true);
+}
+
+static void
+ffmpeg_decode(Decoder &decoder, InputStream &input)
+{
+ AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
+ if (input_format == nullptr)
+ return;
+
+ FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
+ input_format->name, input_format->long_name);
+
+ AvioStream stream(&decoder, input);
+ if (!stream.Open()) {
+ LogError(ffmpeg_domain, "Failed to open stream");
+ return;
+ }
+
+ //ffmpeg works with ours "fileops" helper
+ AVFormatContext *format_context = nullptr;
+ if (mpd_ffmpeg_open_input(&format_context, stream.io,
+ input.GetURI(),
+ input_format) != 0) {
+ LogError(ffmpeg_domain, "Open failed");
+ return;
+ }
+
+ const int find_result =
+ avformat_find_stream_info(format_context, nullptr);
+ if (find_result < 0) {
+ LogError(ffmpeg_domain, "Couldn't find stream info");
+ avformat_close_input(&format_context);
+ return;
+ }
+
+ int audio_stream = ffmpeg_find_audio_stream(format_context);
+ if (audio_stream == -1) {
+ LogError(ffmpeg_domain, "No audio stream inside");
+ avformat_close_input(&format_context);
+ return;
+ }
+
+ AVStream *av_stream = format_context->streams[audio_stream];
+
+ AVCodecContext *codec_context = av_stream->codec;
+
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0)
+ const AVCodecDescriptor *codec_descriptor =
+ avcodec_descriptor_get(codec_context->codec_id);
+ if (codec_descriptor != nullptr)
+ FormatDebug(ffmpeg_domain, "codec '%s'",
+ codec_descriptor->name);
+#else
+ if (codec_context->codec_name[0] != 0)
+ FormatDebug(ffmpeg_domain, "codec '%s'",
+ codec_context->codec_name);
+#endif
+
+ AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
+
+ if (!codec) {
+ LogError(ffmpeg_domain, "Unsupported audio codec");
+ avformat_close_input(&format_context);
+ return;
+ }
+
+ const SampleFormat sample_format =
+ ffmpeg_sample_format(codec_context->sample_fmt);
+ if (sample_format == SampleFormat::UNDEFINED) {
+ // (error message already done by ffmpeg_sample_format())
+ avformat_close_input(&format_context);
+ return;
+ }
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format,
+ codec_context->sample_rate,
+ sample_format,
+ codec_context->channels, error)) {
+ LogError(error);
+ avformat_close_input(&format_context);
+ 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, nullptr);
+ if (open_result < 0) {
+ LogError(ffmpeg_domain, "Could not open codec");
+ avformat_close_input(&format_context);
+ return;
+ }
+
+ const SignedSongTime total_time =
+ format_context->duration != (int64_t)AV_NOPTS_VALUE
+ ? SignedSongTime::FromScale<uint64_t>(format_context->duration,
+ AV_TIME_BASE)
+ : SignedSongTime::Negative();
+
+ decoder_initialized(decoder, audio_format,
+ input.IsSeekable(), total_time);
+
+#if LIBAVUTIL_VERSION_MAJOR >= 53
+ AVFrame *frame = av_frame_alloc();
+#else
+ AVFrame *frame = avcodec_alloc_frame();
+#endif
+ if (!frame) {
+ LogError(ffmpeg_domain, "Could not allocate frame");
+ avformat_close_input(&format_context);
+ return;
+ }
+
+ uint8_t *interleaved_buffer = nullptr;
+ int interleaved_buffer_size = 0;
+
+ DecoderCommand 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,
+ frame,
+ &interleaved_buffer, &interleaved_buffer_size);
+ else
+ cmd = decoder_get_command(decoder);
+
+ av_free_packet(&packet);
+
+ if (cmd == DecoderCommand::SEEK) {
+ int64_t where =
+ time_to_ffmpeg(decoder_seek_time(decoder),
+ av_stream->time_base) +
+ start_time_fallback(*av_stream);
+
+ if (av_seek_frame(format_context, audio_stream, where,
+ AVSEEK_FLAG_ANY) < 0)
+ decoder_seek_error(decoder);
+ else {
+ avcodec_flush_buffers(codec_context);
+ decoder_command_finished(decoder);
+ }
+ }
+ } while (cmd != DecoderCommand::STOP);
+
+#if LIBAVUTIL_VERSION_MAJOR >= 53
+ av_frame_free(&frame);
+#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
+ avcodec_free_frame(&frame);
+#else
+ av_freep(&frame);
+#endif
+ av_freep(&interleaved_buffer);
+
+ avcodec_close(codec_context);
+ avformat_close_input(&format_context);
+}
+
+//no tag reading in ffmpeg, check if playable
+static bool
+ffmpeg_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVInputFormat *input_format = ffmpeg_probe(nullptr, is);
+ if (input_format == nullptr)
+ return false;
+
+ AvioStream stream(nullptr, is);
+ if (!stream.Open())
+ return false;
+
+ AVFormatContext *f = nullptr;
+ if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(),
+ input_format) != 0)
+ return false;
+
+ const int find_result =
+ avformat_find_stream_info(f, nullptr);
+ if (find_result < 0) {
+ avformat_close_input(&f);
+ return false;
+ }
+
+ if (f->duration != (int64_t)AV_NOPTS_VALUE) {
+ const auto duration =
+ SongTime::FromScale<uint64_t>(f->duration,
+ AV_TIME_BASE);
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+ }
+
+ ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
+ int idx = ffmpeg_find_audio_stream(f);
+ if (idx >= 0)
+ ffmpeg_scan_dictionary(f->streams[idx]->metadata,
+ handler, handler_ctx);
+
+ avformat_close_input(&f);
+ 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", "opus", "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",
+ nullptr
+};
+
+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/aacp",
+ "audio/ac3",
+ "audio/aiff"
+ "audio/amr",
+ "audio/basic",
+ "audio/flac",
+ "audio/m4a",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/musepack",
+ "audio/ogg",
+ "audio/opus",
+ "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",
+
+ nullptr
+};
+
+const struct DecoderPlugin 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/plugins/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx
new file mode 100644
index 000000000..0a3e78e4b
--- /dev/null
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_FFMPEG_HXX
+#define MPD_DECODER_FFMPEG_HXX
+
+extern const struct DecoderPlugin ffmpeg_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx
new file mode 100644
index 000000000..a39466945
--- /dev/null
+++ b/src/decoder/plugins/FfmpegMetaData.cxx
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegMetaData.hxx"
+#include "tag/TagTable.hxx"
+#include "tag/TagHandler.hxx"
+
+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 */
+ { nullptr, TAG_NUM_OF_ITEM_TYPES }
+};
+
+static void
+ffmpeg_copy_metadata(TagType type,
+ AVDictionary *m, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ AVDictionaryEntry *mt = nullptr;
+
+ while ((mt = av_dict_get(m, name, mt, 0)) != nullptr)
+ 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 = nullptr;
+
+ while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr)
+ 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(TagType(i), dict, tag_item_names[i],
+ handler, handler_ctx);
+
+ for (const struct tag_table *i = ffmpeg_tags;
+ i->name != nullptr; ++i)
+ ffmpeg_copy_metadata(i->type, dict, i->name,
+ handler, handler_ctx);
+
+ if (handler->pair != nullptr)
+ ffmpeg_scan_pairs(dict, handler, handler_ctx);
+}
diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx
new file mode 100644
index 000000000..5eb41db68
--- /dev/null
+++ b/src/decoder/plugins/FfmpegMetaData.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_METADATA_HXX
+#define MPD_FFMPEG_METADATA_HXX
+
+extern "C" {
+#include <libavutil/dict.h>
+}
+
+/* suppress the ffmpeg compatibility macro */
+#ifdef SampleFormat
+#undef SampleFormat
+#endif
+
+struct tag_handler;
+
+void
+ffmpeg_scan_dictionary(AVDictionary *dict,
+ const tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/decoder/plugins/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx
new file mode 100644
index 000000000..e86f85569
--- /dev/null
+++ b/src/decoder/plugins/FlacCommon.cxx
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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 "util/Error.hxx"
+#include "Log.hxx"
+
+flac_data::flac_data(Decoder &_decoder,
+ InputStream &_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;
+
+ Error error;
+ if (!audio_format_init_checked(data->audio_format,
+ stream_info->sample_rate,
+ flac_sample_format(stream_info->bits_per_sample),
+ stream_info->channels, error)) {
+ LogError(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;
+
+ ReplayGainInfo rgi;
+
+ 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->data.vorbis_comment))
+ decoder_replay_gain(data->decoder, &rgi);
+
+ decoder_mixramp(data->decoder,
+ flac_parse_mixramp(block->data.vorbis_comment));
+
+ data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment);
+ break;
+
+ 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;
+
+ Error error;
+ if (!audio_format_init_checked(data->audio_format,
+ header->sample_rate,
+ flac_sample_format(header->bits_per_sample),
+ header->channels, error)) {
+ LogError(error);
+ data->unsupported = true;
+ return false;
+ }
+
+ data->frame_size = data->audio_format.GetFrameSize();
+
+ const auto duration = SongTime::FromScale<uint64_t>(data->total_frames,
+ data->audio_format.sample_rate);
+
+ decoder_initialized(data->decoder, data->audio_format,
+ data->input_stream.IsSeekable(),
+ duration);
+
+ 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)
+{
+ 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;
+
+ auto cmd = decoder_data(data->decoder, data->input_stream,
+ buffer, buffer_size,
+ bit_rate);
+ data->next_frame += frame->header.blocksize;
+ switch (cmd) {
+ case DecoderCommand::NONE:
+ case DecoderCommand::START:
+ break;
+
+ case DecoderCommand::STOP:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+
+ case DecoderCommand::SEEK:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+ }
+
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
diff --git a/src/decoder/plugins/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx
new file mode 100644
index 000000000..34ce0a3fc
--- /dev/null
+++ b/src/decoder/plugins/FlacCommon.hxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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>
+
+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;
+
+ Decoder &decoder;
+ InputStream &input_stream;
+
+ Tag tag;
+
+ flac_data(Decoder &decoder, InputStream &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/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx
new file mode 100644
index 000000000..eea813401
--- /dev/null
+++ b/src/decoder/plugins/FlacDecoderPlugin.cxx
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "FlacDecoderPlugin.h"
+#include "FlacDomain.hxx"
+#include "FlacCommon.hxx"
+#include "FlacMetadata.hxx"
+#include "OggCodec.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#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;
+ }
+
+ LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
+}
+
+static void flacMetadata(gcc_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(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.Read(path_fs.c_str())) {
+ FormatDebug(flac_domain,
+ "Failed to read FLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static bool
+flac_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.Read(is)) {
+ FormatDebug(flac_domain,
+ "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) {
+ LogError(flac_domain,
+ "FLAC__stream_decoder_new() failed");
+ return nullptr;
+ }
+
+ if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
+ LogDebug(flac_domain,
+ "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)) {
+ LogWarning(flac_domain, "problem reading metadata");
+ return false;
+ }
+
+ if (data->initialized) {
+ /* done */
+
+ const auto duration2 =
+ SongTime::FromScale<uint64_t>(data->total_frames,
+ data->audio_format.sample_rate);
+
+ decoder_initialized(data->decoder, data->audio_format,
+ data->input_stream.IsSeekable(),
+ duration2);
+ return true;
+ }
+
+ if (data->input_stream.IsSeekable())
+ /* 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)
+{
+ Decoder &decoder = data->decoder;
+
+ data->first_frame = t_start;
+
+ while (true) {
+ DecoderCommand cmd;
+ 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 == DecoderCommand::SEEK) {
+ FLAC__uint64 seek_sample = t_start +
+ decoder_seek_where_frame(decoder);
+ 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 == DecoderCommand::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) == DecoderCommand::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(Decoder &decoder,
+ InputStream &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);
+ LogWarning(flac_domain,
+ 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(Decoder &decoder, InputStream &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(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.ReadOgg(path_fs.c_str())) {
+ FormatDebug(flac_domain,
+ "Failed to read OggFLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static bool
+oggflac_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ FlacMetadataChain chain;
+ if (!chain.ReadOgg(is)) {
+ FormatDebug(flac_domain,
+ "Failed to read OggFLAC tags: %s",
+ chain.GetStatusString());
+ return false;
+ }
+
+ chain.Scan(handler, handler_ctx);
+ return true;
+}
+
+static void
+oggflac_decode(Decoder &decoder, InputStream &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.LockRewind(IgnoreError());
+
+ 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 DecoderPlugin 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 DecoderPlugin 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/plugins/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h
new file mode 100644
index 000000000..fcdecf869
--- /dev/null
+++ b/src/decoder/plugins/FlacDecoderPlugin.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_FLAC_H
+#define MPD_DECODER_FLAC_H
+
+extern const struct DecoderPlugin flac_decoder_plugin;
+extern const struct DecoderPlugin oggflac_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx
new file mode 100644
index 000000000..fc5cc5498
--- /dev/null
+++ b/src/decoder/plugins/FlacDomain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FlacDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain flac_domain("flac");
diff --git a/src/decoder/plugins/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx
new file mode 100644
index 000000000..a06c6c6b4
--- /dev/null
+++ b/src/decoder/plugins/FlacDomain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FLAC_DOMAIN_HXX
+#define MPD_FLAC_DOMAIN_HXX
+
+#include "check.h"
+
+extern const class Domain flac_domain;
+
+#endif
diff --git a/src/decoder/plugins/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx
new file mode 100644
index 000000000..0dd895798
--- /dev/null
+++ b/src/decoder/plugins/FlacIOHandle.cxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FlacIOHandle.hxx"
+#include "util/Error.hxx"
+#include "Compiler.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+static size_t
+FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
+{
+ InputStream *is = (InputStream *)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!) */
+
+ Error error;
+ while (p < end) {
+ size_t nbytes = is->LockRead(p, end - p, error);
+ if (nbytes == 0) {
+ if (!error.IsDefined())
+ /* end of file */
+ break;
+
+ if (error.IsDomain(errno_domain))
+ errno = error.GetCode();
+ else
+ /* just some random non-zero
+ errno value */
+ errno = EINVAL;
+ 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)
+{
+ InputStream *is = (InputStream *)handle;
+
+ offset_type offset = _offset;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is->GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is->KnownSize())
+ return -1;
+
+ offset += is->GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return is->LockSeek(offset, IgnoreError()) ? 0 : -1;
+}
+
+static FLAC__int64
+FlacIOTell(FLAC__IOHandle handle)
+{
+ InputStream *is = (InputStream *)handle;
+
+ return is->GetOffset();
+}
+
+static int
+FlacIOEof(FLAC__IOHandle handle)
+{
+ InputStream *is = (InputStream *)handle;
+
+ return is->LockIsEOF();
+}
+
+static int
+FlacIOClose(gcc_unused FLAC__IOHandle handle)
+{
+ /* no-op because the libFLAC caller is repsonsible for closing
+ the #InputStream */
+
+ 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/plugins/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx
new file mode 100644
index 000000000..90acc66af
--- /dev/null
+++ b/src/decoder/plugins/FlacIOHandle.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FLAC_IO_HANDLE_HXX
+#define MPD_FLAC_IO_HANDLE_HXX
+
+#include "Compiler.h"
+#include "input/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(InputStream &is)
+{
+ return (FLAC__IOHandle)&is;
+}
+
+static inline const FLAC__IOCallbacks &
+GetFlacIOCallbacks(const InputStream &is)
+{
+ return is.IsSeekable()
+ ? flac_io_callbacks_seekable
+ : flac_io_callbacks;
+}
+
+#endif
diff --git a/src/decoder/plugins/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx
new file mode 100644
index 000000000..5b4c3104d
--- /dev/null
+++ b/src/decoder/plugins/FlacInput.cxx
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FlacInput.hxx"
+#include "FlacDomain.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+#include "Compiler.h"
+
+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.LockIsEOF() ||
+ (decoder != nullptr &&
+ decoder_get_command(*decoder) != DecoderCommand::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.IsSeekable())
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
+
+ ::Error error;
+ if (!input_stream.LockSeek(absolute_byte_offset, error)) {
+ LogError(error);
+ 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.IsSeekable())
+ return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
+
+ *absolute_byte_offset = (FLAC__uint64)input_stream.GetOffset();
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+FLAC__StreamDecoderLengthStatus
+FlacInput::Length(FLAC__uint64 *stream_length)
+{
+ if (!input_stream.KnownSize())
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
+
+ *stream_length = (FLAC__uint64)input_stream.GetSize();
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+}
+
+FLAC__bool
+FlacInput::Eof()
+{
+ return (decoder != nullptr &&
+ decoder_get_command(*decoder) != DecoderCommand::NONE &&
+ decoder_get_command(*decoder) != DecoderCommand::SEEK) ||
+ input_stream.LockIsEOF();
+}
+
+void
+FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
+{
+ if (decoder == nullptr ||
+ decoder_get_command(*decoder) != DecoderCommand::STOP)
+ LogWarning(flac_domain,
+ 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/plugins/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx
new file mode 100644
index 000000000..427abccb4
--- /dev/null
+++ b/src/decoder/plugins/FlacInput.hxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FLAC_INPUT_HXX
+#define MPD_FLAC_INPUT_HXX
+
+#include <FLAC/stream_decoder.h>
+
+struct Decoder;
+class InputStream;
+
+/**
+ * This class wraps an #InputStream in libFLAC stream decoder
+ * callbacks.
+ */
+class FlacInput {
+ Decoder *const decoder;
+
+ InputStream &input_stream;
+
+public:
+ FlacInput(InputStream &_input_stream,
+ 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/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx
new file mode 100644
index 000000000..03e276dce
--- /dev/null
+++ b/src/decoder/plugins/FlacMetadata.cxx
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FlacMetadata.hxx"
+#include "XiphTags.hxx"
+#include "MixRampInfo.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagTable.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
+#include "tag/VorbisComment.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
+#include "ReplayGainInfo.hxx"
+#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
+
+bool
+flac_parse_replay_gain(ReplayGainInfo &rgi,
+ const FLAC__StreamMetadata_VorbisComment &vc)
+{
+ rgi.Clear();
+
+ bool found = false;
+
+ const auto *comments = vc.comments;
+ for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i)
+ if (ParseReplayGainVorbis(rgi,
+ (const char *)comments[i].entry))
+ found = true;
+
+ return found;
+}
+
+MixRampInfo
+flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc)
+{
+ MixRampInfo mix_ramp;
+
+ const auto *comments = vc.comments;
+ for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i)
+ ParseMixRampVorbis(mix_ramp,
+ (const char *)comments[i].entry);
+
+ return mix_ramp;
+}
+
+/**
+ * Checks if the specified name matches the entry's name, and if yes,
+ * returns the comment value;
+ */
+static const char *
+flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name)
+{
+ return vorbis_comment_value((const char *)entry->entry, name);
+}
+
+/**
+ * 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, TagType tag_type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const char *value = flac_comment_value(entry, name);
+ if (value != nullptr) {
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
+ 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) {
+ const char *comment = (const char *)entry->entry;
+ const SplitString split(comment, '=');
+ if (split.IsDefined() && !split.IsEmpty())
+ tag_handler_invoke_pair(handler, handler_ctx,
+ split.GetFirst(),
+ split.GetSecond());
+ }
+
+ 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], (TagType)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);
+}
+
+gcc_pure
+static inline SongTime
+flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ assert(stream_info->sample_rate > 0);
+
+ return SongTime::FromScale<uint64_t>(stream_info->total_samples,
+ stream_info->sample_rate);
+}
+
+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;
+ }
+}
+
+Tag
+flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment)
+{
+ TagBuilder tag_builder;
+ flac_scan_comments(comment, &add_tag_handler, &tag_builder);
+ return tag_builder.Commit();
+}
+
+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/plugins/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx
new file mode 100644
index 000000000..d791fa34e
--- /dev/null
+++ b/src/decoder/plugins/FlacMetadata.hxx
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FLAC_METADATA_H
+#define MPD_FLAC_METADATA_H
+
+#include "Compiler.h"
+#include "FlacIOHandle.hxx"
+
+#include <FLAC/metadata.h>
+
+#include <assert.h>
+
+struct tag_handler;
+class MixRampInfo;
+
+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(InputStream &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(InputStream &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 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;
+struct ReplayGainInfo;
+
+bool
+flac_parse_replay_gain(ReplayGainInfo &rgi,
+ const FLAC__StreamMetadata_VorbisComment &vc);
+
+MixRampInfo
+flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc);
+
+Tag
+flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment);
+
+void
+flac_scan_metadata(const FLAC__StreamMetadata *block,
+ const tag_handler *handler, void *handler_ctx);
+
+#endif
diff --git a/src/decoder/plugins/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx
new file mode 100644
index 000000000..311500f26
--- /dev/null
+++ b/src/decoder/plugins/FlacPcm.cxx
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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/plugins/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx
new file mode 100644
index 000000000..30c318725
--- /dev/null
+++ b/src/decoder/plugins/FlacPcm.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
new file mode 100644
index 000000000..f19ac5bf4
--- /dev/null
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FluidsynthDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "Log.hxx"
+
+#include <fluidsynth.h>
+
+static constexpr Domain fluidsynth_domain("fluidsynth");
+
+static unsigned sample_rate;
+static const char *soundfont_path;
+
+/**
+ * Convert a fluidsynth log level to a GLib log level.
+ */
+static LogLevel
+fluidsynth_level_to_mpd(enum fluid_log_level level)
+{
+ switch (level) {
+ case FLUID_PANIC:
+ case FLUID_ERR:
+ return LogLevel::ERROR;
+
+ case FLUID_WARN:
+ return LogLevel::WARNING;
+
+ case FLUID_INFO:
+ return LogLevel::INFO;
+
+ case FLUID_DBG:
+ case LAST_LOG_LEVEL:
+ return LogLevel::DEBUG;
+ }
+
+ /* invalid fluidsynth log level */
+ return LogLevel::INFO;
+}
+
+/**
+ * The fluidsynth logging callback. It forwards messages to the GLib
+ * logging library.
+ */
+static void
+fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data)
+{
+ Log(fluidsynth_domain,
+ fluidsynth_level_to_mpd(fluid_log_level(level)),
+ message);
+}
+
+static bool
+fluidsynth_init(const config_param &param)
+{
+ Error error;
+
+ sample_rate = param.GetBlockValue("sample_rate", 48000u);
+ if (!audio_check_sample_rate(sample_rate, error)) {
+ LogError(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(Decoder &decoder, Path 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;
+
+ /* 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) {
+ LogWarning(fluidsynth_domain, "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.c_str());
+ if (ret != 0) {
+ LogWarning(fluidsynth_domain, "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) {
+ LogWarning(fluidsynth_domain, "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,
+ SignedSongTime::Negative());
+
+ DecoderCommand cmd;
+ while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
+ int16_t buffer[2048];
+ const unsigned max_frames = ARRAY_SIZE(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 != DecoderCommand::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(Path path_fs,
+ gcc_unused const struct tag_handler *handler,
+ gcc_unused void *handler_ctx)
+{
+ return fluid_is_midifile(path_fs.c_str());
+}
+
+static const char *const fluidsynth_suffixes[] = {
+ "mid",
+ nullptr
+};
+
+const struct DecoderPlugin fluidsynth_decoder_plugin = {
+ "fluidsynth",
+ fluidsynth_init,
+ nullptr,
+ nullptr,
+ fluidsynth_file_decode,
+ fluidsynth_scan_file,
+ nullptr,
+ nullptr,
+ fluidsynth_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx
new file mode 100644
index 000000000..cd8ec2d62
--- /dev/null
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_FLUIDSYNTH_HXX
+#define MPD_DECODER_FLUIDSYNTH_HXX
+
+extern const struct DecoderPlugin fluidsynth_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx
new file mode 100644
index 000000000..cc6ce5e5d
--- /dev/null
+++ b/src/decoder/plugins/GmeDecoderPlugin.cxx
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "GmeDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "fs/Path.hxx"
+#include "util/Alloc.hxx"
+#include "util/FormatString.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gme/gme.h>
+
+#define SUBTUNE_PREFIX "tune_"
+
+static constexpr Domain gme_domain("gme");
+
+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(Path path_fs)
+{
+ const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
+ char *path_container = xstrdup(path_fs.c_str());
+
+ char pat[64];
+ snprintf(pat, sizeof(pat), "%s%s",
+ "*/" SUBTUNE_PREFIX "???.",
+ subtune_suffix);
+ GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
+ if (!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, nullptr)) {
+ g_pattern_spec_free(path_with_subtune);
+ return path_container;
+ }
+
+ 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(Path path_fs)
+{
+ const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
+
+ char pat[64];
+ snprintf(pat, sizeof(pat), "%s%s",
+ "*/" SUBTUNE_PREFIX "???.",
+ subtune_suffix);
+ GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
+
+ if (g_pattern_match(path_with_subtune,
+ path_fs.length(), path_fs.data(), nullptr)) {
+ char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX);
+ g_pattern_spec_free(path_with_subtune);
+ if (!sub)
+ return 0;
+
+ sub += strlen("/" SUBTUNE_PREFIX);
+ int song_num = strtol(sub, nullptr, 10);
+
+ return song_num - 1;
+ } else {
+ g_pattern_spec_free(path_with_subtune);
+ return 0;
+ }
+}
+
+static char *
+gme_container_scan(Path path_fs, const unsigned int tnum)
+{
+ Music_Emu *emu;
+ const char *gme_err = gme_open_file(path_fs.c_str(), &emu,
+ GME_SAMPLE_RATE);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return nullptr;
+ }
+
+ const unsigned num_songs = gme_track_count(emu);
+ gme_delete(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.c_str());
+ if (tnum <= num_songs){
+ return FormatNew(SUBTUNE_PREFIX "%03u.%s",
+ tnum, subtune_suffix);
+ } else
+ return nullptr;
+}
+
+static void
+gme_file_decode(Decoder &decoder, Path 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);
+ free(path_container);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return;
+ }
+
+ gme_info_t *ti;
+ const int song_num = get_song_num(path_fs);
+ gme_err = gme_track_info(emu, &ti, song_num);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ gme_delete(emu);
+ return;
+ }
+
+ const SignedSongTime song_len = ti->length > 0
+ ? SignedSongTime::FromMS(ti->length)
+ : SignedSongTime::Negative();
+
+ /* initialize the MPD decoder */
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
+ SampleFormat::S16, GME_CHANNELS,
+ error)) {
+ LogError(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)
+ LogWarning(gme_domain, gme_err);
+
+ if (ti->length > 0)
+ gme_set_fade(emu, ti->length);
+
+ /* play */
+ DecoderCommand cmd;
+ do {
+ short buf[GME_BUFFER_SAMPLES];
+ gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return;
+ }
+
+ cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0);
+ if (cmd == DecoderCommand::SEEK) {
+ unsigned where = decoder_seek_time(decoder).ToMS();
+ gme_err = gme_seek(emu, where);
+ if (gme_err != nullptr)
+ LogWarning(gme_domain, gme_err);
+ decoder_command_finished(decoder);
+ }
+
+ if (gme_track_ended(emu))
+ break;
+ } while (cmd != DecoderCommand::STOP);
+
+ gme_free_info(ti);
+ gme_delete(emu);
+}
+
+static bool
+gme_scan_file(Path 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);
+ free(path_container);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return false;
+ }
+
+ const int song_num = get_song_num(path_fs);
+
+ gme_info_t *ti;
+ gme_err = gme_track_info(emu, &ti, song_num);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ gme_delete(emu);
+ return false;
+ }
+
+ assert(ti != nullptr);
+
+ if (ti->length > 0)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime::FromMS(ti->length));
+
+ if (ti->song != nullptr) {
+ if (gme_track_count(emu) > 1) {
+ /* start numbering subtunes from 1 */
+ char tag_title[1024];
+ snprintf(tag_title, sizeof(tag_title),
+ "%s (%d/%d)",
+ ti->song, song_num + 1,
+ gme_track_count(emu));
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, 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 DecoderPlugin gme_decoder_plugin;
+const struct DecoderPlugin 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/plugins/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx
new file mode 100644
index 000000000..f4885b6e4
--- /dev/null
+++ b/src/decoder/plugins/GmeDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_GME_HXX
+#define MPD_DECODER_GME_HXX
+
+extern const struct DecoderPlugin gme_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx
new file mode 100644
index 000000000..de6c9b127
--- /dev/null
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -0,0 +1,1104 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MadDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "tag/TagId3.hxx"
+#include "tag/TagRva2.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
+#include "CheckAudioFormat.hxx"
+#include "util/StringUtil.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <mad.h>
+
+#ifdef HAVE_ID3TAG
+#include <id3tag.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+static constexpr unsigned long FRAMES_CUSHION = 2000;
+
+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 */
+static constexpr unsigned DECODERDELAY = 529;
+
+static constexpr bool DEFAULT_GAPLESS_MP3_PLAYBACK = true;
+
+static constexpr Domain mad_domain("mad");
+
+static bool gapless_playback;
+
+gcc_const
+static SongTime
+ToSongTime(mad_timer_t t)
+{
+ return SongTime::FromMS(mad_timer_count(t, MAD_UNITS_MILLISECONDS));
+}
+
+static inline int32_t
+mad_fixed_to_24_sample(mad_fixed_t sample)
+{
+ static constexpr unsigned bits = 24;
+ static constexpr mad_fixed_t MIN = -MAD_F_ONE;
+ static constexpr mad_fixed_t 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)
+{
+ for (unsigned i = start; i < end; ++i)
+ for (unsigned 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;
+}
+
+struct MadDecoder {
+ static constexpr size_t READ_BUFFER_SIZE = 40960;
+ static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048;
+
+ 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];
+ SignedSongTime total_time;
+ SongTime elapsed_time;
+ SongTime seek_time;
+ 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_first_frame;
+ bool decoded_first_frame;
+ unsigned long bit_rate;
+ Decoder *const decoder;
+ InputStream &input_stream;
+ enum mad_layer layer;
+
+ MadDecoder(Decoder *decoder, InputStream &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
+ offset_type ThisFrameOffset() const;
+
+ gcc_pure
+ offset_type RestIncludingThisFrame() const;
+
+ /**
+ * Attempt to calulcate the length of the song from filesize
+ */
+ void FileSizeToSongLength();
+
+ bool DecodeFirstFrame(Tag **tag);
+
+ gcc_pure
+ long TimeToFrame(SongTime t) const;
+
+ void UpdateTimerNextFrame();
+
+ /**
+ * Sends the synthesized current frame via decoder_data().
+ */
+ DecoderCommand SendPCM(unsigned i, unsigned pcm_length);
+
+ /**
+ * Synthesize the current frame and send it via
+ * decoder_data().
+ */
+ DecoderCommand SyncAndSend();
+
+ bool Read();
+};
+
+MadDecoder::MadDecoder(Decoder *_decoder,
+ InputStream &_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_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)
+{
+ Error error;
+ if (!input_stream.LockSeek(offset, error))
+ 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(ReplayGainInfo &rgi,
+ struct id3_tag *tag)
+{
+ bool found = false;
+
+ rgi.Clear();
+
+ struct id3_frame *frame;
+ for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ if (frame->nfields < 3)
+ continue;
+
+ char *const key = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[1]));
+ char *const value = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[2]));
+
+ if (ParseReplayGainTag(rgi, key, value))
+ found = true;
+
+ free(key);
+ free(value);
+ }
+
+ return found ||
+ /* fall back on RVA2 if no replaygain tags found */
+ tag_rva2_parse(tag, rgi);
+}
+#endif
+
+#ifdef HAVE_ID3TAG
+gcc_pure
+static MixRampInfo
+parse_id3_mixramp(struct id3_tag *tag)
+{
+ MixRampInfo result;
+
+ struct id3_frame *frame;
+ for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ if (frame->nfields < 3)
+ continue;
+
+ char *const key = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[1]));
+ char *const value = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[2]));
+
+ ParseMixRampTag(result, key, value);
+
+ free(key);
+ free(value);
+ }
+
+ return result;
+}
+#endif
+
+inline void
+MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
+{
+#ifdef HAVE_ID3TAG
+ id3_byte_t *allocated = nullptr;
+
+ const id3_length_t count = stream.bufend - stream.this_frame;
+
+ const id3_byte_t *id3_data;
+ if (tagsize <= count) {
+ id3_data = stream.this_frame;
+ mad_stream_skip(&(stream), tagsize);
+ } else {
+ allocated = new id3_byte_t[tagsize];
+ memcpy(allocated, stream.this_frame, count);
+ mad_stream_skip(&(stream), count);
+
+ if (!decoder_read_full(decoder, input_stream,
+ allocated + count, tagsize - count)) {
+ LogDebug(mad_domain, "error parsing ID3 tag");
+ delete[] allocated;
+ return;
+ }
+
+ id3_data = allocated;
+ }
+
+ struct id3_tag *const id3_tag = id3_tag_parse(id3_data, tagsize);
+ if (id3_tag == nullptr) {
+ delete[] 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) {
+ ReplayGainInfo rgi;
+
+ if (parse_id3_replay_gain_info(rgi, id3_tag)) {
+ decoder_replay_gain(*decoder, &rgi);
+ found_replay_gain = true;
+ }
+
+ decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag));
+ }
+
+ id3_tag_delete(id3_tag);
+
+ delete[] 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);
+ decoder_skip(decoder, input_stream, tagsize - count);
+ }
+#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 */
+
+static enum mp3_action
+RecoverFrameError(struct mad_stream &stream)
+{
+ if (MAD_RECOVERABLE(stream.error))
+ return DECODE_SKIP;
+ else if (stream.error == MAD_ERROR_BUFLEN)
+ return DECODE_CONT;
+
+ FormatWarning(mad_domain,
+ "unrecoverable frame level error: %s",
+ mad_stream_errorstr(&stream));
+ return DECODE_BREAK;
+}
+
+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;
+ }
+ }
+
+ return RecoverFrameError(stream);
+ }
+
+ 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;
+ }
+ }
+
+ return RecoverFrameError(stream);
+ }
+
+ return DECODE_OK;
+}
+
+/* xing stuff stolen from alsaplayer, and heavily modified by jat */
+static constexpr unsigned XI_MAGIC = (('X' << 8) | 'i');
+static constexpr unsigned NG_MAGIC = (('n' << 8) | 'g');
+static constexpr unsigned IN_MAGIC = (('I' << 8) | 'n');
+static constexpr unsigned 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 */
+};
+
+static const unsigned XING_FRAMES = 1;
+static const unsigned XING_BYTES = 2;
+static const unsigned XING_TOC = 4;
+static const unsigned XING_SCALE = 8;
+
+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)
+{
+ int bitlen = *oldbitlen;
+
+ if (bitlen < 16)
+ return false;
+
+ const unsigned long 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 (unsigned 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 */
+ const int 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)
+{
+ /* 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 (unsigned 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 (!StringStartsWith(lame->encoder, "LAME"))
+ return false;
+
+ if (sscanf(lame->encoder+4, "%u.%u",
+ &lame->version.major, &lame->version.minor) != 2)
+ return false;
+
+ FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")",
+ 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 */
+ int adj = 0;
+ 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 */
+ FormatDebug(mad_domain, "LAME peak found: %f", lame->peak);
+
+ lame->track_gain = 0;
+ unsigned name = mad_bit_read(ptr, 3); /* gain name */
+ unsigned orig = mad_bit_read(ptr, 3); /* gain originator */
+ unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
+ int gain = mad_bit_read(ptr, 9); /* gain*10 */
+ if (gain && name == 1 && orig != 0) {
+ lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
+ FormatDebug(mad_domain, "LAME track gain found: %f",
+ 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;
+ FormatDebug(mad_domain, "LAME album gain found: %f",
+ 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);
+
+ FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i",
+ lame->encoder_delay, lame->encoder_padding);
+
+ mad_bit_read(ptr, 80);
+
+ lame->crc = mad_bit_read(ptr, 16);
+
+ *bitlen -= 216;
+
+ return true;
+}
+
+static inline SongTime
+mp3_frame_duration(const struct mad_frame *frame)
+{
+ return ToSongTime(frame->header.duration);
+}
+
+inline offset_type
+MadDecoder::ThisFrameOffset() const
+{
+ auto offset = input_stream.GetOffset();
+
+ if (stream.this_frame != nullptr)
+ offset -= stream.bufend - stream.this_frame;
+ else
+ offset -= stream.bufend - stream.buffer;
+
+ return offset;
+}
+
+inline offset_type
+MadDecoder::RestIncludingThisFrame() const
+{
+ return input_stream.GetSize() - ThisFrameOffset();
+}
+
+inline void
+MadDecoder::FileSizeToSongLength()
+{
+ if (input_stream.KnownSize()) {
+ offset_type rest = RestIncludingThisFrame();
+
+ const SongTime frame_duration = mp3_frame_duration(&frame);
+ const SongTime duration =
+ SongTime::FromScale<uint64_t>(rest,
+ frame.header.bitrate / 8);
+ total_time = duration;
+
+ max_frames = (frame_duration.IsPositive()
+ ? duration.count() / frame_duration.count()
+ : 0)
+ + FRAMES_CUSHION;
+ } else {
+ max_frames = FRAMES_CUSHION;
+ total_time = SignedSongTime::Negative();
+ }
+}
+
+inline bool
+MadDecoder::DecodeFirstFrame(Tag **tag)
+{
+ struct xing xing;
+ xing.frames = 0;
+
+ while (true) {
+ enum mp3_action ret;
+ 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;
+ }
+
+ struct mad_bitptr ptr = stream.anc_ptr;
+ int bitlen = stream.anc_bitlen;
+
+ FileSizeToSongLength();
+
+ /*
+ * if an xing tag exists, use that!
+ */
+ if (parse_xing(&xing, &ptr, &bitlen)) {
+ 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 = ToSongTime(duration);
+ max_frames = xing.frames;
+ }
+
+ struct lame lame;
+ if (parse_lame(&lame, &ptr, &bitlen)) {
+ if (gapless_playback && input_stream.IsSeekable()) {
+ 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) {
+ ReplayGainInfo rgi;
+ rgi.Clear();
+ 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) {
+ FormatWarning(mad_domain,
+ "mp3 file header indicates too many frames: %lu",
+ 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 std::pair<bool, SignedSongTime>
+mad_decoder_total_file_time(InputStream &is)
+{
+ MadDecoder data(nullptr, is);
+ return data.DecodeFirstFrame(nullptr)
+ ? std::make_pair(true, data.total_time)
+ : std::make_pair(false, SignedSongTime::Negative());
+}
+
+long
+MadDecoder::TimeToFrame(SongTime t) const
+{
+ unsigned long i;
+
+ for (i = 0; i < highest_frame; ++i) {
+ auto frame_time = ToSongTime(times[i]);
+ 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 = ToSongTime(timer);
+}
+
+DecoderCommand
+MadDecoder::SendPCM(unsigned i, unsigned pcm_length)
+{
+ unsigned max_samples = sizeof(output_buffer) /
+ sizeof(output_buffer[0]) /
+ MAD_NCHANNELS(&frame.header);
+
+ while (i < pcm_length) {
+ 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);
+
+ auto cmd = decoder_data(*decoder, input_stream, output_buffer,
+ sizeof(output_buffer[0]) * num_samples,
+ bit_rate / 1000);
+ if (cmd != DecoderCommand::NONE)
+ return cmd;
+ }
+
+ return DecoderCommand::NONE;
+}
+
+inline DecoderCommand
+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 DecoderCommand::NONE;
+ } else if ((drop_end_frames > 0) &&
+ (current_frame == (max_frames + 1 - drop_end_frames))) {
+ /* stop decoding, effectively dropping all remaining
+ frames */
+ return DecoderCommand::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;
+ }
+
+ auto cmd = SendPCM(i, pcm_length);
+ if (cmd != DecoderCommand::NONE)
+ return cmd;
+
+ if (drop_end_samples &&
+ (current_frame == max_frames - drop_end_frames))
+ /* stop decoding, effectively dropping
+ * all remaining samples */
+ return DecoderCommand::STOP;
+
+ return DecoderCommand::NONE;
+}
+
+inline bool
+MadDecoder::Read()
+{
+ UpdateTimerNextFrame();
+
+ switch (mute_frame) {
+ DecoderCommand cmd;
+
+ case MUTEFRAME_SKIP:
+ mute_frame = MUTEFRAME_NONE;
+ break;
+ case MUTEFRAME_SEEK:
+ if (elapsed_time >= seek_time)
+ mute_frame = MUTEFRAME_NONE;
+ break;
+ case MUTEFRAME_NONE:
+ cmd = SyncAndSend();
+ if (cmd == DecoderCommand::SEEK) {
+ assert(input_stream.IsSeekable());
+
+ unsigned long j =
+ TimeToFrame(decoder_seek_time(*decoder));
+ if (j < highest_frame) {
+ if (Seek(frame_offsets[j])) {
+ current_frame = j;
+ decoder_command_finished(*decoder);
+ } else
+ decoder_seek_error(*decoder);
+ } else {
+ seek_time = decoder_seek_time(*decoder);
+ mute_frame = MUTEFRAME_SEEK;
+ decoder_command_finished(*decoder);
+ }
+ } else if (cmd != DecoderCommand::NONE)
+ return false;
+ }
+
+ while (true) {
+ enum mp3_action ret;
+ 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;
+
+ const bool skip = ret == DECODE_SKIP;
+
+ if (mute_frame == MUTEFRAME_NONE) {
+ do {
+ ret = DecodeNextFrame();
+ } while (ret == DECODE_CONT);
+ if (ret == DECODE_BREAK)
+ return false;
+ }
+
+ if (!skip && ret == DECODE_OK)
+ return true;
+ }
+}
+
+static void
+mp3_decode(Decoder &decoder, InputStream &input_stream)
+{
+ MadDecoder data(&decoder, input_stream);
+
+ Tag *tag = nullptr;
+ if (!data.DecodeFirstFrame(&tag)) {
+ delete tag;
+
+ if (decoder_get_command(decoder) == DecoderCommand::NONE)
+ LogError(mad_domain,
+ "input/Input does not appear to be a mp3 bit stream");
+ return;
+ }
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format,
+ data.frame.header.samplerate,
+ SampleFormat::S24_P32,
+ MAD_NCHANNELS(&data.frame.header),
+ error)) {
+ LogError(error);
+ delete tag;
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format,
+ input_stream.IsSeekable(),
+ 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(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const auto result = mad_decoder_total_file_time(is);
+ if (!result.first)
+ return false;
+
+ if (!result.second.IsNegative())
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime(result.second));
+ return true;
+}
+
+static const char *const mp3_suffixes[] = { "mp3", "mp2", nullptr };
+static const char *const mp3_mime_types[] = { "audio/mpeg", nullptr };
+
+const struct DecoderPlugin 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/plugins/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx
new file mode 100644
index 000000000..eb2a10d6f
--- /dev/null
+++ b/src/decoder/plugins/MadDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_MAD_HXX
+#define MPD_DECODER_MAD_HXX
+
+extern const struct DecoderPlugin mad_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx
new file mode 100644
index 000000000..85633f1fc
--- /dev/null
+++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MikmodDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "tag/TagHandler.hxx"
+#include "system/FatalError.hxx"
+#include "fs/Path.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <mikmod.h>
+
+#include <assert.h>
+
+static constexpr Domain mikmod_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 bool mikmod_loop;
+static unsigned mikmod_sample_rate;
+
+static bool
+mikmod_decoder_init(const config_param &param)
+{
+ static char params[] = "";
+
+ mikmod_loop = param.GetBlockValue("loop", false);
+ mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u);
+ if (!audio_valid_sample_rate(mikmod_sample_rate))
+ FormatFatalError("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)) {
+ FormatError(mikmod_domain,
+ "Could not init MikMod: %s",
+ MikMod_strerror(MikMod_errno));
+ return false;
+ }
+
+ return true;
+}
+
+static void
+mikmod_decoder_finish(void)
+{
+ MikMod_Exit();
+}
+
+static void
+mikmod_decoder_file_decode(Decoder &decoder, Path path_fs)
+{
+ /* deconstify the path because libmikmod wants a non-const
+ string pointer */
+ char *const path2 = const_cast<char *>(path_fs.c_str());
+
+ MODULE *handle;
+ int ret;
+ SBYTE buffer[MIKMOD_FRAME_SIZE];
+
+ handle = Player_Load(path2, 128, 0);
+
+ if (handle == nullptr) {
+ FormatError(mikmod_domain,
+ "failed to open mod: %s", path_fs.c_str());
+ return;
+ }
+
+ handle->loop = mikmod_loop;
+
+ const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format, false,
+ SignedSongTime::Negative());
+
+ Player_Start(handle);
+
+ DecoderCommand cmd = DecoderCommand::NONE;
+ while (cmd == DecoderCommand::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(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ /* deconstify the path because libmikmod wants a non-const
+ string pointer */
+ char *const path2 = const_cast<char *>(path_fs.c_str());
+
+ MODULE *handle = Player_Load(path2, 128, 0);
+
+ if (handle == nullptr) {
+ FormatDebug(mikmod_domain,
+ "Failed to open file: %s", path_fs.c_str());
+ return false;
+ }
+
+ Player_Free(handle);
+
+ char *title = Player_LoadTitle(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 DecoderPlugin 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/plugins/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx
new file mode 100644
index 000000000..27ba2a823
--- /dev/null
+++ b/src/decoder/plugins/MikmodDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_MIKMOD_HXX
+#define MPD_DECODER_MIKMOD_HXX
+
+extern const struct DecoderPlugin mikmod_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx
new file mode 100644
index 000000000..3e0a41550
--- /dev/null
+++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ModplugDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagHandler.hxx"
+#include "system/FatalError.hxx"
+#include "util/WritableBuffer.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <libmodplug/modplug.h>
+
+
+#include <assert.h>
+
+static constexpr Domain modplug_domain("modplug");
+
+static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
+static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024;
+static constexpr offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024;
+
+static int modplug_loop_count;
+
+static bool
+modplug_decoder_init(const config_param &param)
+{
+ modplug_loop_count = param.GetBlockValue("loop_count", 0);
+ if (modplug_loop_count < -1)
+ FormatFatalError("Invalid loop count in line %d: %i",
+ param.line, modplug_loop_count);
+
+ return true;
+}
+
+static WritableBuffer<uint8_t>
+mod_loadfile(Decoder *decoder, InputStream &is)
+{
+ //known/unknown size, preallocate array, lets read in chunks
+
+ const bool is_stream = !is.KnownSize();
+
+ WritableBuffer<uint8_t> buffer;
+ if (is_stream)
+ buffer.size = MODPLUG_PREALLOC_BLOCK;
+ else {
+ const auto size = is.GetSize();
+
+ if (size == 0) {
+ LogWarning(modplug_domain, "file is empty");
+ return { nullptr, 0 };
+ }
+
+ if (size > MODPLUG_FILE_LIMIT) {
+ LogWarning(modplug_domain, "file too large");
+ return { nullptr, 0 };
+ }
+
+ buffer.size = size;
+ }
+
+ buffer.data = new uint8_t[buffer.size];
+
+ uint8_t *const end = buffer.end();
+ uint8_t *p = buffer.begin();
+
+ while (true) {
+ size_t ret = decoder_read(decoder, is, p, end - p);
+ if (ret == 0) {
+ if (is.LockIsEOF())
+ /* end of file */
+ break;
+
+ /* I/O error - skip this song */
+ delete[] buffer.data;
+ buffer.data = nullptr;
+ return buffer;
+ }
+
+ p += ret;
+ if (p == end) {
+ if (!is_stream)
+ break;
+
+ LogWarning(modplug_domain, "stream too large");
+ delete[] buffer.data;
+ buffer.data = nullptr;
+ return buffer;
+ }
+ }
+
+ buffer.size = p - buffer.data;
+ return buffer;
+}
+
+static ModPlugFile *
+LoadModPlugFile(Decoder *decoder, InputStream &is)
+{
+ const auto buffer = mod_loadfile(decoder, is);
+ if (buffer.IsNull()) {
+ LogWarning(modplug_domain, "could not load stream");
+ return nullptr;
+ }
+
+ ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size);
+ delete[] buffer.data;
+ return f;
+}
+
+static void
+mod_decode(Decoder &decoder, InputStream &is)
+{
+ ModPlug_Settings settings;
+ int ret;
+ char audio_buffer[MODPLUG_FRAME_SIZE];
+
+ ModPlug_GetSettings(&settings);
+ /* alter setting */
+ settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
+ settings.mChannels = 2;
+ settings.mBits = 16;
+ settings.mFrequency = 44100;
+ settings.mLoopCount = modplug_loop_count;
+ /* insert more setting changes here */
+ ModPlug_SetSettings(&settings);
+
+ ModPlugFile *f = LoadModPlugFile(&decoder, is);
+ if (f == nullptr) {
+ LogWarning(modplug_domain, "could not decode stream");
+ return;
+ }
+
+ static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format,
+ is.IsSeekable(),
+ SongTime::FromMS(ModPlug_GetLength(f)));
+
+ DecoderCommand cmd;
+ 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 == DecoderCommand::SEEK) {
+ ModPlug_Seek(f, decoder_seek_time(decoder).ToMS());
+ decoder_command_finished(decoder);
+ }
+
+ } while (cmd != DecoderCommand::STOP);
+
+ ModPlug_Unload(f);
+}
+
+static bool
+modplug_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ ModPlugFile *f = LoadModPlugFile(nullptr, is);
+ if (f == nullptr)
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime::FromMS(ModPlug_GetLength(f)));
+
+ 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 DecoderPlugin modplug_decoder_plugin = {
+ "modplug",
+ modplug_decoder_init,
+ nullptr,
+ mod_decode,
+ nullptr,
+ nullptr,
+ modplug_scan_stream,
+ nullptr,
+ mod_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/plugins/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx
new file mode 100644
index 000000000..08f2ecb12
--- /dev/null
+++ b/src/decoder/plugins/ModplugDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_MODPLUG_HXX
+#define MPD_DECODER_MODPLUG_HXX
+
+extern const struct DecoderPlugin modplug_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
new file mode 100644
index 000000000..befed0f3b
--- /dev/null
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MpcdecDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "Log.hxx"
+
+#include <mpc/mpcdec.h>
+
+#include <math.h>
+
+struct mpc_decoder_data {
+ InputStream &is;
+ Decoder *decoder;
+
+ mpc_decoder_data(InputStream &_is, Decoder *_decoder)
+ :is(_is), decoder(_decoder) {}
+};
+
+static constexpr Domain mpcdec_domain("mpcdec");
+
+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 data->is.LockSeek(offset, IgnoreError());
+}
+
+static mpc_int32_t
+mpc_tell_cb(mpc_reader *reader)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return (long)data->is.GetOffset();
+}
+
+static mpc_bool_t
+mpc_canseek_cb(mpc_reader *reader)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ return data->is.IsSeekable();
+}
+
+static mpc_int32_t
+mpc_getsize_cb(mpc_reader *reader)
+{
+ struct mpc_decoder_data *data =
+ (struct mpc_decoder_data *)reader->data;
+
+ if (!data->is.KnownSize())
+ return -1;
+
+ return data->is.GetSize();
+}
+
+/* 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(Decoder &mpd_decoder, InputStream &is)
+{
+ MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
+
+ mpc_decoder_data data(is, &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) != DecoderCommand::STOP)
+ LogWarning(mpcdec_domain,
+ "Not a valid musepack stream");
+ return;
+ }
+
+ mpc_streaminfo info;
+ mpc_demux_get_info(demux, &info);
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, info.sample_freq,
+ SampleFormat::S24_P32,
+ info.channels, error)) {
+ LogError(error);
+ mpc_demux_exit(demux);
+ return;
+ }
+
+ ReplayGainInfo rgi;
+ rgi.Clear();
+ rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
+ rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
+ rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
+ rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
+
+ decoder_replay_gain(mpd_decoder, &rgi);
+
+ decoder_initialized(mpd_decoder, audio_format,
+ is.IsSeekable(),
+ SongTime::FromS(mpc_streaminfo_get_length(&info)));
+
+ DecoderCommand cmd = DecoderCommand::NONE;
+ do {
+ if (cmd == DecoderCommand::SEEK) {
+ mpc_int64_t where =
+ decoder_seek_where_frame(mpd_decoder);
+ 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) {
+ LogWarning(mpcdec_domain,
+ "Failed to decode sample");
+ break;
+ }
+
+ if (frame.bits == -1)
+ break;
+
+ mpc_uint32_t ret = frame.samples;
+ ret *= info.channels;
+
+ int32_t chunk[ARRAY_SIZE(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 != DecoderCommand::STOP);
+
+ mpc_demux_exit(demux);
+}
+
+static SignedSongTime
+mpcdec_get_file_duration(InputStream &is)
+{
+ mpc_decoder_data data(is, 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 SignedSongTime::Negative();
+
+ mpc_streaminfo info;
+ mpc_demux_get_info(demux, &info);
+ mpc_demux_exit(demux);
+
+ return SongTime::FromS(mpc_streaminfo_get_length(&info));
+}
+
+static bool
+mpcdec_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const auto duration = mpcdec_get_file_duration(is);
+ if (duration.IsNegative())
+ return false;
+
+ tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration));
+ return true;
+}
+
+static const char *const mpcdec_suffixes[] = { "mpc", nullptr };
+
+const struct DecoderPlugin mpcdec_decoder_plugin = {
+ "mpcdec",
+ nullptr,
+ nullptr,
+ mpcdec_decode,
+ nullptr,
+ nullptr,
+ mpcdec_scan_stream,
+ nullptr,
+ mpcdec_suffixes,
+ nullptr,
+};
diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx
new file mode 100644
index 000000000..7f71311fa
--- /dev/null
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_MPCDEC_HXX
+#define MPD_DECODER_MPCDEC_HXX
+
+extern const struct DecoderPlugin mpcdec_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
new file mode 100644
index 000000000..166529a4d
--- /dev/null
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "Mpg123DecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <mpg123.h>
+
+#include <stdio.h>
+
+static constexpr Domain mpg123_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)
+{
+ /* mpg123_open() wants a writable string :-( */
+ char *const path2 = const_cast<char *>(path_fs);
+
+ int error = mpg123_open(handle, path2);
+ if (error != MPG123_OK) {
+ FormatWarning(mpg123_domain,
+ "libmpg123 failed to open %s: %s",
+ path_fs, mpg123_plain_strerror(error));
+ return false;
+ }
+
+ /* obtain the audio format */
+
+ long rate;
+ int channels, encoding;
+ error = mpg123_getformat(handle, &rate, &channels, &encoding);
+ if (error != MPG123_OK) {
+ FormatWarning(mpg123_domain,
+ "mpg123_getformat() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ if (encoding != MPG123_ENC_SIGNED_16) {
+ /* other formats not yet implemented */
+ FormatWarning(mpg123_domain,
+ "expected MPG123_ENC_SIGNED_16, got %d",
+ encoding);
+ return false;
+ }
+
+ Error error2;
+ if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16,
+ channels, error2)) {
+ LogError(error2);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+AddTagItem(TagBuilder &tag, TagType type, const mpg123_string &s)
+{
+ assert(s.p != nullptr);
+ assert(s.size >= s.fill);
+ assert(s.fill > 0);
+
+ tag.AddItem(type, s.p, s.fill - 1);
+}
+
+static void
+AddTagItem(TagBuilder &tag, TagType type, const mpg123_string *s)
+{
+ if (s != nullptr)
+ AddTagItem(tag, type, *s);
+}
+
+static void
+mpd_mpg123_id3v2_tag(Decoder &decoder, const mpg123_id3v2 &id3v2)
+{
+ TagBuilder tag;
+
+ AddTagItem(tag, TAG_TITLE, id3v2.title);
+ AddTagItem(tag, TAG_ARTIST, id3v2.artist);
+ AddTagItem(tag, TAG_ALBUM, id3v2.album);
+ AddTagItem(tag, TAG_DATE, id3v2.year);
+ AddTagItem(tag, TAG_GENRE, id3v2.genre);
+
+ for (size_t i = 0, n = id3v2.comments; i < n; ++i)
+ AddTagItem(tag, TAG_COMMENT, id3v2.comment_list[i].text);
+
+ decoder_tag(decoder, nullptr, tag.Commit());
+}
+
+static void
+mpd_mpg123_id3v2_extras(Decoder &decoder, const mpg123_id3v2 &id3v2)
+{
+ ReplayGainInfo replay_gain;
+ replay_gain.Clear();
+
+ MixRampInfo mix_ramp;
+
+ bool found_replay_gain = false, found_mixramp = false;
+
+ for (size_t i = 0, n = id3v2.extras; i < n; ++i) {
+ if (ParseReplayGainTag(replay_gain,
+ id3v2.extra[i].description.p,
+ id3v2.extra[i].text.p))
+ found_replay_gain = true;
+ else if (ParseMixRampTag(mix_ramp,
+ id3v2.extra[i].description.p,
+ id3v2.extra[i].text.p))
+ found_mixramp = true;
+ }
+
+ if (found_replay_gain)
+ decoder_replay_gain(decoder, &replay_gain);
+
+ if (found_mixramp)
+ decoder_mixramp(decoder, std::move(mix_ramp));
+}
+
+static void
+mpd_mpg123_id3v2(Decoder &decoder, const mpg123_id3v2 &id3v2)
+{
+ mpd_mpg123_id3v2_tag(decoder, id3v2);
+ mpd_mpg123_id3v2_extras(decoder, id3v2);
+}
+
+static void
+mpd_mpg123_meta(Decoder &decoder, mpg123_handle *const handle)
+{
+ if ((mpg123_meta_check(handle) & MPG123_NEW_ID3) == 0)
+ return;
+
+ mpg123_id3v1 *v1;
+ mpg123_id3v2 *v2;
+ if (mpg123_id3(handle, &v1, &v2) != MPG123_OK)
+ return;
+
+ if (v2 != nullptr)
+ mpd_mpg123_id3v2(decoder, *v2);
+}
+
+static void
+mpd_mpg123_file_decode(Decoder &decoder, Path path_fs)
+{
+ /* open the file */
+
+ int error;
+ mpg123_handle *const handle = mpg123_new(nullptr, &error);
+ if (handle == nullptr) {
+ FormatError(mpg123_domain,
+ "mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return;
+ }
+
+ AudioFormat audio_format;
+ if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) {
+ mpg123_delete(handle);
+ return;
+ }
+
+ const off_t num_samples = mpg123_length(handle);
+
+ /* tell MPD core we're ready */
+
+ const auto duration =
+ SongTime::FromScale<uint64_t>(num_samples,
+ audio_format.sample_rate);
+
+ decoder_initialized(decoder, audio_format, true, duration);
+
+ struct mpg123_frameinfo info;
+ 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 */
+ DecoderCommand cmd;
+ do {
+ /* read metadata */
+ mpd_mpg123_meta(decoder, handle);
+
+ /* decode */
+
+ unsigned char buffer[8192];
+ size_t nbytes;
+ error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
+ if (error != MPG123_OK) {
+ if (error != MPG123_DONE)
+ FormatWarning(mpg123_domain,
+ "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 == DecoderCommand::SEEK) {
+ off_t c = decoder_seek_where_frame(decoder);
+ 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 = DecoderCommand::NONE;
+ }
+ } while (cmd == DecoderCommand::NONE);
+
+ /* cleanup */
+
+ mpg123_delete(handle);
+}
+
+static bool
+mpd_mpg123_scan_file(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ int error;
+ mpg123_handle *const handle = mpg123_new(nullptr, &error);
+ if (handle == nullptr) {
+ FormatError(mpg123_domain,
+ "mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ AudioFormat audio_format;
+ if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) {
+ mpg123_delete(handle);
+ return false;
+ }
+
+ const off_t num_samples = mpg123_length(handle);
+ if (num_samples <= 0) {
+ mpg123_delete(handle);
+ return false;
+ }
+
+ /* ID3 tag support not yet implemented */
+
+ mpg123_delete(handle);
+
+ const auto duration =
+ SongTime::FromScale<uint64_t>(num_samples,
+ audio_format.sample_rate);
+
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+ return true;
+}
+
+static const char *const mpg123_suffixes[] = {
+ "mp3",
+ nullptr
+};
+
+const struct DecoderPlugin 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/plugins/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx
new file mode 100644
index 000000000..fd089c6a4
--- /dev/null
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_MPG123_HXX
+#define MPD_DECODER_MPG123_HXX
+
+extern const struct DecoderPlugin mpg123_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx
new file mode 100644
index 000000000..c7f39586e
--- /dev/null
+++ b/src/decoder/plugins/OggCodec.cxx
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
+ */
+
+#include "config.h"
+#include "OggCodec.hxx"
+#include "../DecoderAPI.hxx"
+
+#include <string.h>
+
+enum ogg_codec
+ogg_codec_detect(Decoder *decoder, InputStream &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/plugins/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx
new file mode 100644
index 000000000..3b096561c
--- /dev/null
+++ b/src/decoder/plugins/OggCodec.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
+ */
+
+#ifndef MPD_OGG_CODEC_HXX
+#define MPD_OGG_CODEC_HXX
+
+struct Decoder;
+class InputStream;
+
+enum ogg_codec {
+ OGG_CODEC_UNKNOWN,
+ OGG_CODEC_VORBIS,
+ OGG_CODEC_FLAC,
+ OGG_CODEC_OPUS,
+};
+
+enum ogg_codec
+ogg_codec_detect(Decoder *decoder, InputStream &is);
+
+#endif /* _OGG_COMMON_H */
diff --git a/src/decoder/plugins/OggFind.cxx b/src/decoder/plugins/OggFind.cxx
new file mode 100644
index 000000000..978e1d7cf
--- /dev/null
+++ b/src/decoder/plugins/OggFind.cxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OggFind.hxx"
+#include "OggSyncState.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.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;
+ }
+}
+
+bool
+OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
+ offset_type offset)
+{
+ oy.Reset();
+
+ /* reset the stream to clear any previous partial packet
+ data */
+ ogg_stream_reset(&os);
+
+ return is.LockSeek(offset, IgnoreError()) &&
+ oy.ExpectPageSeekIn(os);
+}
+
+bool
+OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
+ InputStream &is)
+{
+ if (!is.KnownSize())
+ return false;
+
+ if (is.GetRest() < 65536)
+ return OggFindEOS(oy, os, packet);
+
+ if (!is.CheapSeeking())
+ return false;
+
+ return OggSeekPageAtOffset(oy, os, is, is.GetSize() - 65536) &&
+ OggFindEOS(oy, os, packet);
+}
diff --git a/src/decoder/plugins/OggFind.hxx b/src/decoder/plugins/OggFind.hxx
new file mode 100644
index 000000000..2aa4f6c06
--- /dev/null
+++ b/src/decoder/plugins/OggFind.hxx
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OGG_FIND_HXX
+#define MPD_OGG_FIND_HXX
+
+#include "check.h"
+#include "input/Offset.hxx"
+
+#include <ogg/ogg.h>
+
+class OggSyncState;
+class InputStream;
+
+/**
+ * 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);
+
+/**
+ * Seek the #InputStream and find the next Ogg page.
+ */
+bool
+OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
+ offset_type offset);
+
+/**
+ * Try to find the end-of-stream (EOS) packet. Seek to the end of the
+ * file if necessary.
+ *
+ * @return true if the EOS packet was found
+ */
+bool
+OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
+ InputStream &is);
+
+#endif
diff --git a/src/decoder/plugins/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx
new file mode 100644
index 000000000..024902fff
--- /dev/null
+++ b/src/decoder/plugins/OggSyncState.hxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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;
+
+ InputStream &is;
+ Decoder *const decoder;
+
+public:
+ OggSyncState(InputStream &_is, 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/plugins/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx
new file mode 100644
index 000000000..6341b0008
--- /dev/null
+++ b/src/decoder/plugins/OggUtil.cxx
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OggUtil.hxx"
+#include "../DecoderAPI.hxx"
+
+bool
+OggFeed(ogg_sync_state &oy, Decoder *decoder,
+ InputStream &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, InputStream &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, InputStream &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, InputStream &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, InputStream &input_stream)
+{
+ size_t remaining_skipped = 32768;
+
+ 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, InputStream &is)
+{
+ ogg_page page;
+ if (!OggExpectPageSeek(oy, page, decoder, is))
+ return false;
+
+ ogg_stream_pagein(&os, &page);
+ return true;
+}
diff --git a/src/decoder/plugins/OggUtil.hxx b/src/decoder/plugins/OggUtil.hxx
new file mode 100644
index 000000000..94c380ef4
--- /dev/null
+++ b/src/decoder/plugins/OggUtil.hxx
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OGG_UTIL_HXX
+#define MPD_OGG_UTIL_HXX
+
+#include "check.h"
+
+#include <ogg/ogg.h>
+
+#include <stddef.h>
+
+class InputStream;
+struct Decoder;
+
+/**
+ * Feed data from the #InputStream into the #ogg_sync_state.
+ *
+ * @return false on error or end-of-file
+ */
+bool
+OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &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, InputStream &is);
+
+/**
+ * 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, InputStream &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, InputStream &is);
+
+/**
+ * Like OggExpectPage(), but allow skipping garbage (after seeking).
+ */
+bool
+OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
+ Decoder *decoder, InputStream &is);
+
+/**
+ * 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, InputStream &is);
+
+#endif
diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
new file mode 100644
index 000000000..e14827e38
--- /dev/null
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "OpusDecoderPlugin.h"
+#include "OpusDomain.hxx"
+#include "OpusHead.hxx"
+#include "OpusTags.hxx"
+#include "OggFind.hxx"
+#include "OggSyncState.hxx"
+#include "../DecoderAPI.hxx"
+#include "OggCodec.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagBuilder.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <opus.h>
+#include <ogg/ogg.h>
+
+#include <string.h>
+#include <stdio.h>
+
+static constexpr opus_int32 opus_sample_rate = 48000;
+
+/**
+ * Allocate an output buffer for 16 bit PCM samples big enough to hold
+ * a quarter second, larger than 120ms required by libopus.
+ */
+static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4;
+
+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)
+{
+ LogDebug(opus_domain, opus_get_version_string());
+
+ return true;
+}
+
+class MPDOpusDecoder {
+ Decoder &decoder;
+ InputStream &input_stream;
+
+ ogg_stream_state os;
+
+ OpusDecoder *opus_decoder;
+ opus_int16 *output_buffer;
+
+ /**
+ * If non-zero, then a previous Opus stream has been found
+ * already with this number of channels. If opus_decoder is
+ * nullptr, then its end-of-stream packet has been found
+ * already.
+ */
+ unsigned previous_channels;
+
+ bool os_initialized;
+
+ int opus_serialno;
+
+ ogg_int64_t eos_granulepos;
+
+ size_t frame_size;
+
+public:
+ MPDOpusDecoder(Decoder &_decoder,
+ InputStream &_input_stream)
+ :decoder(_decoder), input_stream(_input_stream),
+ opus_decoder(nullptr),
+ output_buffer(nullptr),
+ previous_channels(0),
+ os_initialized(false) {}
+ ~MPDOpusDecoder();
+
+ bool ReadFirstPage(OggSyncState &oy);
+ bool ReadNextPage(OggSyncState &oy);
+
+ DecoderCommand HandlePackets();
+ DecoderCommand HandlePacket(const ogg_packet &packet);
+ DecoderCommand HandleBOS(const ogg_packet &packet);
+ DecoderCommand HandleEOS();
+ DecoderCommand HandleTags(const ogg_packet &packet);
+ DecoderCommand HandleAudio(const ogg_packet &packet);
+
+ bool Seek(OggSyncState &oy, uint64_t where_frame);
+};
+
+MPDOpusDecoder::~MPDOpusDecoder()
+{
+ delete[] 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 DecoderCommand
+MPDOpusDecoder::HandlePackets()
+{
+ ogg_packet packet;
+ while (ogg_stream_packetout(&os, &packet) == 1) {
+ auto cmd = HandlePacket(packet);
+ if (cmd != DecoderCommand::NONE)
+ return cmd;
+ }
+
+ return DecoderCommand::NONE;
+}
+
+inline DecoderCommand
+MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
+{
+ if (packet.e_o_s)
+ return HandleEOS();
+
+ if (packet.b_o_s)
+ return HandleBOS(packet);
+ else if (opus_decoder == nullptr) {
+ LogDebug(opus_domain, "BOS packet expected");
+ return DecoderCommand::STOP;
+ }
+
+ if (IsOpusTags(packet))
+ return HandleTags(packet);
+
+ return HandleAudio(packet);
+}
+
+/**
+ * Load the end-of-stream packet and restore the previous file
+ * position.
+ */
+static bool
+LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
+ ogg_packet &packet)
+{
+ if (!is.CheapSeeking())
+ /* we do this for local files only, because seeking
+ around remote files is expensive and not worth the
+ troubl */
+ return false;
+
+ const auto old_offset = is.GetOffset();
+
+ /* create temporary Ogg objects for seeking and parsing the
+ EOS packet */
+ OggSyncState oy(is, decoder);
+ ogg_stream_state os;
+ ogg_stream_init(&os, serialno);
+
+ bool result = OggSeekFindEOS(oy, os, packet, is);
+ ogg_stream_clear(&os);
+
+ /* restore the previous file position */
+ is.LockSeek(old_offset, IgnoreError());
+
+ return result;
+}
+
+/**
+ * Load the end-of-stream granulepos and restore the previous file
+ * position.
+ *
+ * @return -1 on error
+ */
+gcc_pure
+static ogg_int64_t
+LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno)
+{
+ ogg_packet packet;
+ if (!LoadEOSPacket(is, decoder, serialno, packet))
+ return -1;
+
+ return packet.granulepos;
+}
+
+inline DecoderCommand
+MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
+{
+ assert(packet.b_o_s);
+
+ if (opus_decoder != nullptr || !IsOpusHead(packet)) {
+ LogDebug(opus_domain, "BOS packet must be OpusHead");
+ return DecoderCommand::STOP;
+ }
+
+ unsigned channels;
+ if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
+ !audio_valid_channel_count(channels)) {
+ LogDebug(opus_domain, "Malformed BOS packet");
+ return DecoderCommand::STOP;
+ }
+
+ assert(opus_decoder == nullptr);
+ assert((previous_channels == 0) == (output_buffer == nullptr));
+
+ if (previous_channels != 0 && channels != previous_channels) {
+ FormatWarning(opus_domain,
+ "Next stream has different channels (%u -> %u)",
+ previous_channels, channels);
+ return DecoderCommand::STOP;
+ }
+
+ opus_serialno = os.serialno;
+
+ /* 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) {
+ FormatError(opus_domain, "libopus error: %s",
+ opus_strerror(opus_error));
+ return DecoderCommand::STOP;
+ }
+
+ if (previous_channels != 0) {
+ /* decoder was already initialized by the previous
+ stream; skip the rest of this method */
+ LogDebug(opus_domain, "Found another stream");
+ return decoder_get_command(decoder);
+ }
+
+ eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
+ opus_serialno);
+ const auto duration = eos_granulepos >= 0
+ ? SignedSongTime::FromScale<uint64_t>(eos_granulepos,
+ opus_sample_rate)
+ : SignedSongTime::Negative();
+
+ previous_channels = channels;
+ const AudioFormat audio_format(opus_sample_rate,
+ SampleFormat::S16, channels);
+ decoder_initialized(decoder, audio_format,
+ eos_granulepos > 0, duration);
+ frame_size = audio_format.GetFrameSize();
+
+ output_buffer = new opus_int16[opus_output_buffer_frames
+ * audio_format.channels];
+
+ return decoder_get_command(decoder);
+}
+
+inline DecoderCommand
+MPDOpusDecoder::HandleEOS()
+{
+ if (eos_granulepos < 0 && previous_channels != 0) {
+ /* allow chaining of (unseekable) streams */
+ assert(opus_decoder != nullptr);
+ assert(output_buffer != nullptr);
+
+ opus_decoder_destroy(opus_decoder);
+ opus_decoder = nullptr;
+
+ return decoder_get_command(decoder);
+ }
+
+ return DecoderCommand::STOP;
+}
+
+inline DecoderCommand
+MPDOpusDecoder::HandleTags(const ogg_packet &packet)
+{
+ ReplayGainInfo rgi;
+ rgi.Clear();
+
+ TagBuilder tag_builder;
+
+ DecoderCommand cmd;
+ if (ScanOpusTags(packet.packet, packet.bytes,
+ &rgi,
+ &add_tag_handler, &tag_builder) &&
+ !tag_builder.IsEmpty()) {
+ decoder_replay_gain(decoder, &rgi);
+
+ Tag tag = tag_builder.Commit();
+ cmd = decoder_tag(decoder, input_stream, std::move(tag));
+ } else
+ cmd = decoder_get_command(decoder);
+
+ return cmd;
+}
+
+inline DecoderCommand
+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, opus_output_buffer_frames,
+ 0);
+ if (nframes < 0) {
+ FormatError(opus_domain, "libopus error: %s",
+ opus_strerror(nframes));
+ return DecoderCommand::STOP;
+ }
+
+ if (nframes > 0) {
+ const size_t nbytes = nframes * frame_size;
+ auto cmd = decoder_data(decoder, input_stream,
+ output_buffer, nbytes,
+ 0);
+ if (cmd != DecoderCommand::NONE)
+ return cmd;
+
+ if (packet.granulepos > 0)
+ decoder_timestamp(decoder,
+ double(packet.granulepos)
+ / opus_sample_rate);
+ }
+
+ return DecoderCommand::NONE;
+}
+
+bool
+MPDOpusDecoder::Seek(OggSyncState &oy, uint64_t where_frame)
+{
+ assert(eos_granulepos > 0);
+ assert(input_stream.IsSeekable());
+ assert(input_stream.KnownSize());
+
+ const ogg_int64_t where_granulepos(where_frame);
+
+ /* interpolate the file offset where we expect to find the
+ given granule position */
+ /* TODO: implement binary search */
+ offset_type offset(where_granulepos * input_stream.GetSize()
+ / eos_granulepos);
+
+ return OggSeekPageAtOffset(oy, os, input_stream, offset);
+}
+
+static void
+mpd_opus_stream_decode(Decoder &decoder,
+ InputStream &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.LockRewind(IgnoreError());
+
+ MPDOpusDecoder d(decoder, input_stream);
+ OggSyncState oy(input_stream, &decoder);
+
+ if (!d.ReadFirstPage(oy))
+ return;
+
+ while (true) {
+ auto cmd = d.HandlePackets();
+ if (cmd == DecoderCommand::SEEK) {
+ if (d.Seek(oy, decoder_seek_where_frame(decoder)))
+ decoder_command_finished(decoder);
+ else
+ decoder_seek_error(decoder);
+
+ continue;
+ }
+
+ if (cmd != DecoderCommand::NONE)
+ break;
+
+ if (!d.ReadNextPage(oy))
+ break;
+ }
+}
+
+static bool
+mpd_opus_scan_stream(InputStream &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,
+ nullptr,
+ handler, handler_ctx))
+ result = false;
+
+ break;
+ }
+ }
+
+ if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) {
+ const auto duration =
+ SongTime::FromScale<uint64_t>(packet.granulepos,
+ opus_sample_rate);
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+ }
+
+ ogg_stream_clear(&os);
+
+ return result;
+}
+
+static const char *const opus_suffixes[] = {
+ "opus",
+ "ogg",
+ "oga",
+ nullptr
+};
+
+static const char *const opus_mime_types[] = {
+ /* the official MIME type (RFC 5334) */
+ "audio/ogg",
+
+ /* deprecated (RFC 5334) */
+ "application/ogg",
+
+ /* deprecated; from an early draft */
+ "audio/opus",
+ nullptr
+};
+
+const struct DecoderPlugin 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/plugins/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h
new file mode 100644
index 000000000..260dab99a
--- /dev/null
+++ b/src/decoder/plugins/OpusDecoderPlugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_OPUS_H
+#define MPD_DECODER_OPUS_H
+
+extern const struct DecoderPlugin opus_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx
new file mode 100644
index 000000000..1efd64a48
--- /dev/null
+++ b/src/decoder/plugins/OpusDomain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpusDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain opus_domain("opus");
diff --git a/src/decoder/plugins/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx
new file mode 100644
index 000000000..fb19e0301
--- /dev/null
+++ b/src/decoder/plugins/OpusDomain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPUS_DOMAIN_HXX
+#define MPD_OPUS_DOMAIN_HXX
+
+#include "check.h"
+
+extern const class Domain opus_domain;
+
+#endif
diff --git a/src/decoder/plugins/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx
new file mode 100644
index 000000000..bfa41d618
--- /dev/null
+++ b/src/decoder/plugins/OpusHead.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpusHead.hxx"
+
+#include <stdint.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/plugins/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx
new file mode 100644
index 000000000..c478d8d90
--- /dev/null
+++ b/src/decoder/plugins/OpusHead.hxx
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/plugins/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx
new file mode 100644
index 000000000..c5b8e9107
--- /dev/null
+++ b/src/decoder/plugins/OpusReader.hxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPUS_READER_HXX
+#define MPD_OPUS_READER_HXX
+
+#include "check.h"
+
+#include <algorithm>
+
+#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];
+ *std::copy_n(src, length, dest) = 0;
+ return dest;
+ }
+};
+
+#endif
diff --git a/src/decoder/plugins/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx
new file mode 100644
index 000000000..aff5479c0
--- /dev/null
+++ b/src/decoder/plugins/OpusTags.cxx
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpusTags.hxx"
+#include "OpusReader.hxx"
+#include "XiphTags.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/Tag.hxx"
+#include "ReplayGainInfo.hxx"
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+gcc_pure
+static TagType
+ParseOpusTagName(const char *name)
+{
+ TagType type = tag_name_parse_i(name);
+ if (type != TAG_NUM_OF_ITEM_TYPES)
+ return type;
+
+ return tag_table_lookup_i(xiph_tags, name);
+}
+
+static void
+ScanOneOpusTag(const char *name, const char *value,
+ ReplayGainInfo *rgi,
+ const struct tag_handler *handler, void *ctx)
+{
+ if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) {
+ /* R128_TRACK_GAIN is a Q7.8 fixed point number in
+ dB */
+
+ char *endptr;
+ long l = strtol(value, &endptr, 10);
+ if (endptr > value && *endptr == 0)
+ rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.;
+ }
+
+ tag_handler_invoke_pair(handler, ctx, name, value);
+
+ if (handler->tag != nullptr) {
+ TagType t = ParseOpusTagName(name);
+ if (t != TAG_NUM_OF_ITEM_TYPES)
+ tag_handler_invoke_tag(handler, ctx, t, value);
+ }
+}
+
+bool
+ScanOpusTags(const void *data, size_t size,
+ ReplayGainInfo *rgi,
+ 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, rgi, handler, ctx);
+ }
+
+ delete[] p;
+ }
+
+ return true;
+}
diff --git a/src/decoder/plugins/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx
new file mode 100644
index 000000000..be3ac3a8d
--- /dev/null
+++ b/src/decoder/plugins/OpusTags.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPUS_TAGS_HXX
+#define MPD_OPUS_TAGS_HXX
+
+#include "check.h"
+
+#include <stddef.h>
+
+struct ReplayGainInfo;
+
+bool
+ScanOpusTags(const void *data, size_t size,
+ ReplayGainInfo *rgi,
+ const struct tag_handler *handler, void *ctx);
+
+#endif
diff --git a/src/decoder/plugins/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx
new file mode 100644
index 000000000..c07a7b9b1
--- /dev/null
+++ b/src/decoder/plugins/PcmDecoderPlugin.cxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PcmDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.hxx"
+#include "util/ByteReverse.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+static void
+pcm_stream_decode(Decoder &decoder, InputStream &is)
+{
+ static constexpr AudioFormat audio_format = {
+ 44100,
+ SampleFormat::S16,
+ 2,
+ };
+
+ const char *const mime = is.GetMimeType();
+ const bool reverse_endian = mime != nullptr &&
+ strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
+
+ const auto frame_size = audio_format.GetFrameSize();
+
+ const auto total_time = is.KnownSize()
+ ? SignedSongTime::FromScale<uint64_t>(is.GetSize() / frame_size,
+ audio_format.sample_rate)
+ : SignedSongTime::Negative();
+
+ decoder_initialized(decoder, audio_format,
+ is.IsSeekable(), total_time);
+
+ DecoderCommand cmd;
+ do {
+ char buffer[4096];
+
+ size_t nbytes = decoder_read(decoder, is,
+ buffer, sizeof(buffer));
+
+ if (nbytes == 0 && is.LockIsEOF())
+ 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 == DecoderCommand::SEEK) {
+ uint64_t frame = decoder_seek_where_frame(decoder);
+ offset_type offset = frame * frame_size;
+
+ Error error;
+ if (is.LockSeek(offset, error)) {
+ decoder_command_finished(decoder);
+ } else {
+ LogError(error);
+ decoder_seek_error(decoder);
+ }
+
+ cmd = DecoderCommand::NONE;
+ }
+ } while (cmd == DecoderCommand::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 DecoderPlugin pcm_decoder_plugin = {
+ "pcm",
+ nullptr,
+ nullptr,
+ pcm_stream_decode,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ pcm_mime_types,
+};
diff --git a/src/decoder/plugins/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx
new file mode 100644
index 000000000..3582e5856
--- /dev/null
+++ b/src/decoder/plugins/PcmDecoderPlugin.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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 DecoderPlugin pcm_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx
new file mode 100644
index 000000000..8435f095f
--- /dev/null
+++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SidplayDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "tag/TagHandler.hxx"
+#include "fs/Path.hxx"
+#include "util/FormatString.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include <sidplay/sidplay2.h>
+#include <sidplay/builders/resid.h>
+#include <sidplay/utils/SidTuneMod.h>
+
+#define SUBTUNE_PREFIX "tune_"
+
+static constexpr Domain sidplay_domain("sidplay");
+
+static GPatternSpec *path_with_subtune;
+static const char *songlength_file;
+static GKeyFile *songlength_database;
+
+static bool all_files_are_containers;
+static unsigned default_songlength;
+
+static bool filter_setting;
+
+static GKeyFile *
+sidplay_load_songlength_db(const char *path)
+{
+ GError *error = nullptr;
+ gchar *data;
+ gsize size;
+
+ if (!g_file_get_contents(path, &data, &size, &error)) {
+ FormatError(sidplay_domain,
+ "unable to read songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ return nullptr;
+ }
+
+ /* replace any ; comment characters with # */
+ for (gsize i = 0; i < size; i++)
+ if (data[i] == ';')
+ data[i] = '#';
+
+ GKeyFile *db = g_key_file_new();
+ bool success = g_key_file_load_from_data(db, data, size,
+ G_KEY_FILE_NONE, &error);
+ g_free(data);
+ if (!success) {
+ FormatError(sidplay_domain,
+ "unable to parse songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ g_key_file_free(db);
+ return nullptr;
+ }
+
+ g_key_file_set_list_separator(db, ' ');
+ return db;
+}
+
+static bool
+sidplay_init(const config_param &param)
+{
+ /* read the songlengths database file */
+ songlength_file = param.GetBlockValue("songlength_database");
+ if (songlength_file != nullptr)
+ songlength_database = sidplay_load_songlength_db(songlength_file);
+
+ default_songlength = param.GetBlockValue("default_songlength", 0u);
+
+ all_files_are_containers =
+ param.GetBlockValue("all_files_are_containers", true);
+
+ path_with_subtune=g_pattern_spec_new(
+ "*/" SUBTUNE_PREFIX "???.sid");
+
+ filter_setting = param.GetBlockValue("filter", true);
+
+ return true;
+}
+
+static void
+sidplay_finish()
+{
+ g_pattern_spec_free(path_with_subtune);
+
+ if(songlength_database)
+ g_key_file_free(songlength_database);
+}
+
+/**
+ * returns the file path stripped of any /tune_xxx.sid subtune
+ * suffix
+ */
+static char *
+get_container_name(Path path_fs)
+{
+ char *path_container = strdup(path_fs.c_str());
+
+ if(!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, nullptr))
+ return path_container;
+
+ char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if(ptr) *ptr='\0';
+
+ return path_container;
+}
+
+/**
+ * returns tune number from file.sid/tune_xxx.sid style path or 1 if
+ * no subtune is appended
+ */
+static unsigned
+get_song_num(const char *path_fs)
+{
+ if(g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, nullptr)) {
+ char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ if(!sub) return 1;
+
+ sub+=strlen("/" SUBTUNE_PREFIX);
+ int song_num=strtol(sub, nullptr, 10);
+
+ if (errno == EINVAL)
+ return 1;
+ else
+ return song_num;
+ } else
+ return 1;
+}
+
+/* get the song length in seconds */
+static SignedSongTime
+get_song_length(Path path_fs)
+{
+ if (songlength_database == nullptr)
+ return SignedSongTime::Negative();
+
+ char *sid_file = get_container_name(path_fs);
+ SidTuneMod tune(sid_file);
+ free(sid_file);
+ if(!tune) {
+ LogWarning(sidplay_domain,
+ "failed to load file for calculating md5 sum");
+ return SignedSongTime::Negative();
+ }
+ char md5sum[SIDTUNE_MD5_LENGTH+1];
+ tune.createMD5(md5sum);
+
+ const unsigned song_num = get_song_num(path_fs.c_str());
+
+ gsize num_items;
+ gchar **values=g_key_file_get_string_list(songlength_database,
+ "Database", md5sum, &num_items, nullptr);
+ if(!values || song_num>num_items) {
+ g_strfreev(values);
+ return SignedSongTime::Negative();
+ }
+
+ int minutes=strtol(values[song_num-1], nullptr, 10);
+ if(errno==EINVAL) minutes=0;
+
+ int seconds;
+ char *ptr=strchr(values[song_num-1], ':');
+ if(ptr) {
+ seconds=strtol(ptr+1, nullptr, 10);
+ if(errno==EINVAL) seconds=0;
+ } else
+ seconds=0;
+
+ g_strfreev(values);
+
+ return SignedSongTime::FromS((minutes * 60) + seconds);
+}
+
+static void
+sidplay_file_decode(Decoder &decoder, Path path_fs)
+{
+ int channels;
+
+ /* load the tune */
+
+ char *path_container=get_container_name(path_fs);
+ SidTune tune(path_container, nullptr, true);
+ free(path_container);
+ if (!tune) {
+ LogWarning(sidplay_domain, "failed to load file");
+ return;
+ }
+
+ const int song_num = get_song_num(path_fs.c_str());
+ tune.selectSong(song_num);
+
+ auto duration = get_song_length(path_fs);
+ if (duration.IsNegative() && default_songlength > 0)
+ duration = SongTime::FromS(default_songlength);
+
+ /* initialize the player */
+
+ sidplay2 player;
+ int iret = player.load(&tune);
+ if (iret != 0) {
+ FormatWarning(sidplay_domain,
+ "sidplay2.load() failed: %s", player.error());
+ return;
+ }
+
+ /* initialize the builder */
+
+ ReSIDBuilder builder("ReSID");
+ if (!builder) {
+ LogWarning(sidplay_domain,
+ "failed to initialize ReSIDBuilder");
+ return;
+ }
+
+ builder.create(player.info().maxsids);
+ if (!builder) {
+ LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
+ return;
+ }
+
+ builder.filter(filter_setting);
+ if (!builder) {
+ LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
+ return;
+ }
+
+ /* configure the player */
+
+ sid2_config_t config = player.config();
+
+ config.clockDefault = SID2_CLOCK_PAL;
+ config.clockForced = true;
+ config.clockSpeed = SID2_CLOCK_CORRECT;
+ config.frequency = 48000;
+ config.optimisation = SID2_DEFAULT_OPTIMISATION;
+
+ config.precision = 16;
+ config.sidDefault = SID2_MOS6581;
+ config.sidEmulation = &builder;
+ config.sidModel = SID2_MODEL_CORRECT;
+ config.sidSamples = true;
+ config.sampleFormat = IsLittleEndian()
+ ? SID2_LITTLE_SIGNED
+ : SID2_BIG_SIGNED;
+ if (tune.isStereo()) {
+ config.playback = sid2_stereo;
+ channels = 2;
+ } else {
+ config.playback = sid2_mono;
+ channels = 1;
+ }
+
+ iret = player.config(config);
+ if (iret != 0) {
+ FormatWarning(sidplay_domain,
+ "sidplay2.config() failed: %s", player.error());
+ return;
+ }
+
+ /* initialize the MPD decoder */
+
+ const AudioFormat audio_format(48000, SampleFormat::S16, channels);
+ assert(audio_format.IsValid());
+
+ decoder_initialized(decoder, audio_format, true, duration);
+
+ /* .. and play */
+
+ const unsigned timebase = player.timebase();
+ const unsigned end = duration.IsNegative()
+ ? 0u
+ : duration.ToScale<uint64_t>(timebase);
+
+ DecoderCommand cmd;
+ do {
+ char buffer[4096];
+ size_t nbytes;
+
+ nbytes = player.play(buffer, sizeof(buffer));
+ if (nbytes == 0)
+ break;
+
+ decoder_timestamp(decoder, (double)player.time() / timebase);
+
+ cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0);
+
+ if (cmd == DecoderCommand::SEEK) {
+ unsigned data_time = player.time();
+ unsigned target_time =
+ decoder_seek_time(decoder).ToScale(timebase);
+
+ /* can't rewind so return to zero and seek forward */
+ if(target_time<data_time) {
+ player.stop();
+ data_time=0;
+ }
+
+ /* ignore data until target time is reached */
+ while(data_time<target_time) {
+ nbytes=player.play(buffer, sizeof(buffer));
+ if(nbytes==0)
+ break;
+ data_time = player.time();
+ }
+
+ decoder_command_finished(decoder);
+ }
+
+ if (end > 0 && player.time() >= end)
+ break;
+
+ } while (cmd != DecoderCommand::STOP);
+}
+
+static bool
+sidplay_scan_file(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const int song_num = get_song_num(path_fs.c_str());
+ char *path_container=get_container_name(path_fs);
+
+ SidTune tune(path_container, nullptr, true);
+ free(path_container);
+ if (!tune)
+ return false;
+
+ const SidTuneInfo &info = tune.getInfo();
+
+ /* title */
+ const char *title;
+ if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr)
+ title=info.infoString[0];
+ else
+ title="";
+
+ if(info.songs>1) {
+ char tag_title[1024];
+ snprintf(tag_title, sizeof(tag_title),
+ "%s (%d/%d)",
+ title, song_num, info.songs);
+ tag_handler_invoke_tag(handler, handler_ctx,
+ TAG_TITLE, tag_title);
+ } else
+ tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
+
+ /* artist */
+ if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
+ info.infoString[1]);
+
+ /* track */
+ char track[16];
+ sprintf(track, "%d", song_num);
+ tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
+
+ /* time */
+ const auto duration = get_song_length(path_fs);
+ if (!duration.IsNegative())
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime(duration));
+
+ return true;
+}
+
+static char *
+sidplay_container_scan(Path path_fs, const unsigned int tnum)
+{
+ SidTune tune(path_fs.c_str(), nullptr, true);
+ if (!tune)
+ return nullptr;
+
+ const SidTuneInfo &info=tune.getInfo();
+
+ /* Don't treat sids containing a single tune
+ as containers */
+ if(!all_files_are_containers && info.songs<2)
+ return nullptr;
+
+ /* Construct container/tune path names, eg.
+ Delta.sid/tune_001.sid */
+ if(tnum<=info.songs) {
+ return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum);
+ } else
+ return nullptr;
+}
+
+static const char *const sidplay_suffixes[] = {
+ "sid",
+ "mus",
+ "str",
+ "prg",
+ "P00",
+ nullptr
+};
+
+extern const struct DecoderPlugin sidplay_decoder_plugin;
+const struct DecoderPlugin sidplay_decoder_plugin = {
+ "sidplay",
+ sidplay_init,
+ sidplay_finish,
+ nullptr, /* stream_decode() */
+ sidplay_file_decode,
+ sidplay_scan_file,
+ nullptr, /* stream_tag() */
+ sidplay_container_scan,
+ sidplay_suffixes,
+ nullptr, /* mime_types */
+};
diff --git a/src/decoder/plugins/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx
new file mode 100644
index 000000000..58786e646
--- /dev/null
+++ b/src/decoder/plugins/SidplayDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_SIDPLAY_HXX
+#define MPD_DECODER_SIDPLAY_HXX
+
+extern const struct DecoderPlugin sidplay_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx
new file mode 100644
index 000000000..5518c70be
--- /dev/null
+++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SndfileDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <sndfile.h>
+
+static constexpr Domain sndfile_domain("sndfile");
+
+static bool
+sndfile_init(gcc_unused const config_param &param)
+{
+ LogDebug(sndfile_domain, sf_version_string());
+ return true;
+}
+
+struct SndfileInputStream {
+ Decoder *const decoder;
+ InputStream &is;
+
+ size_t Read(void *buffer, size_t size) {
+ /* libsndfile chokes on partial reads; therefore
+ always force full reads */
+ return decoder_read_full(decoder, is, buffer, size)
+ ? size
+ : 0;
+ }
+};
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+ const InputStream &is = sis.is;
+
+ if (!is.KnownSize())
+ return -1;
+
+ return is.GetSize();
+}
+
+static sf_count_t
+sndfile_vio_seek(sf_count_t _offset, int whence, void *user_data)
+{
+ SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+ InputStream &is = sis.is;
+
+ offset_type offset = _offset;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is.KnownSize())
+ return -1;
+
+ offset += is.GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ Error error;
+ if (!is.LockSeek(offset, error)) {
+ LogError(error, "Seek failed");
+ return -1;
+ }
+
+ return is.GetOffset();
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+
+ return sis.Read(ptr, count);
+}
+
+static sf_count_t
+sndfile_vio_write(gcc_unused const void *ptr,
+ gcc_unused sf_count_t count,
+ gcc_unused void *user_data)
+{
+ /* no writing! */
+ return -1;
+}
+
+static sf_count_t
+sndfile_vio_tell(void *user_data)
+{
+ SndfileInputStream &sis = *(SndfileInputStream *)user_data;
+ const InputStream &is = sis.is;
+
+ return is.GetOffset();
+}
+
+/**
+ * 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 constexpr SongTime
+sndfile_duration(const SF_INFO &info)
+{
+ return SongTime::FromScale<uint64_t>(info.frames, info.samplerate);
+}
+
+gcc_pure
+static SampleFormat
+sndfile_sample_format(const SF_INFO &info)
+{
+ switch (info.format & SF_FORMAT_SUBMASK) {
+ case SF_FORMAT_PCM_S8:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_16:
+ return SampleFormat::S16;
+
+ case SF_FORMAT_FLOAT:
+ case SF_FORMAT_DOUBLE:
+ return SampleFormat::FLOAT;
+
+ default:
+ return SampleFormat::S32;
+ }
+}
+
+static sf_count_t
+sndfile_read_frames(SNDFILE *sf, SampleFormat format,
+ void *buffer, sf_count_t n_frames)
+{
+ switch (format) {
+ case SampleFormat::S16:
+ return sf_readf_short(sf, (short *)buffer, n_frames);
+
+ case SampleFormat::S32:
+ return sf_readf_int(sf, (int *)buffer, n_frames);
+
+ case SampleFormat::FLOAT:
+ return sf_readf_float(sf, (float *)buffer, n_frames);
+
+ default:
+ assert(false);
+ gcc_unreachable();
+ }
+}
+
+static void
+sndfile_stream_decode(Decoder &decoder, InputStream &is)
+{
+ SF_INFO info;
+
+ info.format = 0;
+
+ SndfileInputStream sis{&decoder, is};
+ SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
+ if (sf == nullptr) {
+ FormatWarning(sndfile_domain, "sf_open_virtual() failed: %s",
+ sf_strerror(nullptr));
+ return;
+ }
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, info.samplerate,
+ sndfile_sample_format(info),
+ info.channels, error)) {
+ LogError(error);
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format, info.seekable,
+ sndfile_duration(info));
+
+ char buffer[16384];
+
+ const size_t frame_size = audio_format.GetFrameSize();
+ const sf_count_t read_frames = sizeof(buffer) / frame_size;
+
+ DecoderCommand cmd;
+ do {
+ sf_count_t num_frames =
+ sndfile_read_frames(sf,
+ audio_format.format,
+ buffer, read_frames);
+ if (num_frames <= 0)
+ break;
+
+ cmd = decoder_data(decoder, is,
+ buffer, num_frames * frame_size,
+ 0);
+ if (cmd == DecoderCommand::SEEK) {
+ sf_count_t c = decoder_seek_where_frame(decoder);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DecoderCommand::NONE;
+ }
+ } while (cmd == DecoderCommand::NONE);
+
+ sf_close(sf);
+}
+
+static void
+sndfile_handle_tag(SNDFILE *sf, int str, TagType tag,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const char *value = sf_get_string(sf, str);
+ if (value != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx, tag, value);
+}
+
+static constexpr struct {
+ int8_t str;
+ TagType tag;
+} sndfile_tags[] = {
+ { SF_STR_TITLE, TAG_TITLE },
+ { SF_STR_ARTIST, TAG_ARTIST },
+ { SF_STR_COMMENT, TAG_COMMENT },
+ { SF_STR_DATE, TAG_DATE },
+ { SF_STR_ALBUM, TAG_ALBUM },
+ { SF_STR_TRACKNUMBER, TAG_TRACK },
+ { SF_STR_GENRE, TAG_GENRE },
+};
+
+static bool
+sndfile_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ SF_INFO info;
+
+ info.format = 0;
+
+ SndfileInputStream sis{nullptr, is};
+ SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
+ if (sf == nullptr)
+ return false;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ FormatWarning(sndfile_domain,
+ "Invalid sample rate in %s", is.GetURI());
+ return false;
+ }
+
+ tag_handler_invoke_duration(handler, handler_ctx,
+ sndfile_duration(info));
+
+ for (auto i : sndfile_tags)
+ sndfile_handle_tag(sf, i.str, i.tag, handler, handler_ctx);
+
+ 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 DecoderPlugin sndfile_decoder_plugin = {
+ "sndfile",
+ sndfile_init,
+ nullptr,
+ sndfile_stream_decode,
+ nullptr,
+ nullptr,
+ sndfile_scan_stream,
+ nullptr,
+ sndfile_suffixes,
+ sndfile_mime_types,
+};
diff --git a/src/decoder/plugins/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx
new file mode 100644
index 000000000..d56acdd5a
--- /dev/null
+++ b/src/decoder/plugins/SndfileDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_SNDFILE_HXX
+#define MPD_DECODER_SNDFILE_HXX
+
+extern const struct DecoderPlugin sndfile_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx
new file mode 100644
index 000000000..062f46acf
--- /dev/null
+++ b/src/decoder/plugins/VorbisComments.cxx
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VorbisComments.hxx"
+#include "XiphTags.hxx"
+#include "tag/TagTable.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/VorbisComment.hxx"
+#include "tag/ReplayGain.hxx"
+#include "ReplayGainInfo.hxx"
+#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
+
+#include <stddef.h>
+#include <stdlib.h>
+
+bool
+vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments)
+{
+ rgi.Clear();
+
+ bool found = false;
+
+ while (*comments) {
+ if (ParseReplayGainVorbis(rgi, *comments))
+ 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, TagType tag_type,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const char *value;
+
+ value = vorbis_comment_value(comment, name);
+ if (value != nullptr) {
+ 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 != nullptr) {
+ const SplitString split(comment, '=');
+ if (split.IsDefined() && !split.IsEmpty())
+ tag_handler_invoke_pair(handler, handler_ctx,
+ split.GetFirst(),
+ split.GetSecond());
+ }
+
+ for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++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], TagType(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)
+{
+ TagBuilder tag_builder;
+ vorbis_comments_scan(comments, &add_tag_handler, &tag_builder);
+ return tag_builder.IsEmpty()
+ ? nullptr
+ : tag_builder.CommitNew();
+}
diff --git a/src/decoder/plugins/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx
new file mode 100644
index 000000000..893c89277
--- /dev/null
+++ b/src/decoder/plugins/VorbisComments.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_VORBIS_COMMENTS_HXX
+#define MPD_VORBIS_COMMENTS_HXX
+
+#include "check.h"
+
+struct ReplayGainInfo;
+struct tag_handler;
+struct Tag;
+
+bool
+vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments);
+
+void
+vorbis_comments_scan(char **comments,
+ const tag_handler *handler, void *handler_ctx);
+
+Tag *
+vorbis_comments_to_tag(char **comments);
+
+#endif
diff --git a/src/decoder/plugins/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx
new file mode 100644
index 000000000..e0d3d1374
--- /dev/null
+++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VorbisDecoderPlugin.h"
+#include "VorbisComments.hxx"
+#include "VorbisDomain.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "OggCodec.hxx"
+#include "util/Error.hxx"
+#include "util/Macros.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "Log.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 <errno.h>
+
+struct VorbisInputStream {
+ Decoder *const decoder;
+
+ InputStream &input_stream;
+ bool seekable;
+
+ VorbisInputStream(Decoder *_decoder, InputStream &_is)
+ :decoder(_decoder), input_stream(_is),
+ seekable(input_stream.CheapSeeking()) {}
+};
+
+static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ VorbisInputStream *vis = (VorbisInputStream *)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)
+{
+ VorbisInputStream *vis = (VorbisInputStream *)data;
+ InputStream &is = vis->input_stream;
+
+ if (!vis->seekable ||
+ (vis->decoder != nullptr &&
+ decoder_get_command(*vis->decoder) == DecoderCommand::STOP))
+ return -1;
+
+ offset_type offset = _offset;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is.KnownSize())
+ return -1;
+
+ offset += is.GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return is.LockSeek(offset, IgnoreError())
+ ? 0 : -1;
+}
+
+/* TODO: check Ogg libraries API and see if we can just not have this func */
+static int ogg_close_cb(gcc_unused void *data)
+{
+ return 0;
+}
+
+static long ogg_tell_cb(void *data)
+{
+ VorbisInputStream *vis = (VorbisInputStream *)data;
+
+ return (long)vis->input_stream.GetOffset();
+}
+
+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(VorbisInputStream *vis, OggVorbis_File *vf)
+{
+ int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks);
+ if (ret < 0) {
+ if (vis->decoder == nullptr ||
+ decoder_get_command(*vis->decoder) == DecoderCommand::NONE)
+ FormatWarning(vorbis_domain,
+ "Failed to open Ogg Vorbis stream: %s",
+ vorbis_strerror(ret));
+ return false;
+ }
+
+ return true;
+}
+
+static void
+vorbis_send_comments(Decoder &decoder, InputStream &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 *gcc_restrict d = dest;
+ for (const float *gcc_restrict s = *src, *s_end = s + nframes;
+ s != s_end; ++s, d += channels)
+ *d = *s;
+ }
+}
+#endif
+
+/* public */
+
+static bool
+vorbis_init(gcc_unused const config_param &param)
+{
+#ifndef HAVE_TREMOR
+ LogDebug(vorbis_domain, vorbis_version_string());
+#endif
+ return true;
+}
+
+gcc_pure
+static SignedSongTime
+vorbis_duration(OggVorbis_File &vf)
+{
+ auto total = ov_time_total(&vf, -1);
+ return total >= 0
+ ? SignedSongTime::FromS(total)
+ : SignedSongTime::Negative();
+}
+
+static void
+vorbis_stream_decode(Decoder &decoder,
+ InputStream &input_stream)
+{
+ if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS)
+ return;
+
+ /* rewind the stream, because ogg_codec_detect() has
+ moved it */
+ input_stream.LockRewind(IgnoreError());
+
+ VorbisInputStream vis(&decoder, input_stream);
+ OggVorbis_File vf;
+ if (!vorbis_is_open(&vis, &vf))
+ return;
+
+ const vorbis_info *vi = ov_info(&vf, -1);
+ if (vi == nullptr) {
+ LogWarning(vorbis_domain, "ov_info() has failed");
+ return;
+ }
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format, vi->rate,
+#ifdef HAVE_TREMOR
+ SampleFormat::S16,
+#else
+ SampleFormat::FLOAT,
+#endif
+ vi->channels, error)) {
+ LogError(error);
+ return;
+ }
+
+ decoder_initialized(decoder, audio_format, vis.seekable,
+ vorbis_duration(vf));
+
+#ifdef HAVE_TREMOR
+ char buffer[4096];
+#else
+ float buffer[2048];
+ const int frames_per_buffer =
+ ARRAY_SIZE(buffer) / audio_format.channels;
+ const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels;
+#endif
+
+ int prev_section = -1;
+ unsigned kbit_rate = 0;
+
+ DecoderCommand cmd = decoder_get_command(decoder);
+ do {
+ if (cmd == DecoderCommand::SEEK) {
+ auto seek_where = decoder_seek_where_frame(decoder);
+ if (0 == ov_pcm_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),
+ IsBigEndian(), 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 == nullptr) {
+ LogWarning(vorbis_domain,
+ "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 */
+ LogWarning(vorbis_domain,
+ "audio format change, stopping here");
+ break;
+ }
+
+ char **comments = ov_comment(&vf, -1)->user_comments;
+ vorbis_send_comments(decoder, input_stream, comments);
+
+ ReplayGainInfo 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 != DecoderCommand::STOP);
+
+ ov_clear(&vf);
+}
+
+static bool
+vorbis_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ VorbisInputStream vis(nullptr, is);
+ OggVorbis_File vf;
+
+ if (!vorbis_is_open(&vis, &vf))
+ return false;
+
+ const auto total = ov_time_total(&vf, -1);
+ if (total >= 0)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime::FromS(total));
+
+ 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", nullptr
+};
+
+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",
+ nullptr
+};
+
+const struct DecoderPlugin vorbis_decoder_plugin = {
+ "vorbis",
+ vorbis_init,
+ nullptr,
+ vorbis_stream_decode,
+ nullptr,
+ nullptr,
+ vorbis_scan_stream,
+ nullptr,
+ vorbis_suffixes,
+ vorbis_mime_types
+};
diff --git a/src/decoder/plugins/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h
new file mode 100644
index 000000000..b54df2e97
--- /dev/null
+++ b/src/decoder/plugins/VorbisDecoderPlugin.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_VORBIS_H
+#define MPD_DECODER_VORBIS_H
+
+extern const struct DecoderPlugin vorbis_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx
new file mode 100644
index 000000000..e3d880efa
--- /dev/null
+++ b/src/decoder/plugins/VorbisDomain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VorbisDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain vorbis_domain("vorbis");
diff --git a/src/decoder/plugins/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx
new file mode 100644
index 000000000..48715e328
--- /dev/null
+++ b/src/decoder/plugins/VorbisDomain.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_VORBIS_DOMAIN_HXX
+#define MPD_VORBIS_DOMAIN_HXX
+
+#include "check.h"
+
+class Domain;
+
+extern const Domain vorbis_domain;
+
+#endif
diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx
new file mode 100644
index 000000000..67859bbd2
--- /dev/null
+++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "WavpackDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "CheckAudioFormat.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/ApeTag.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "Log.hxx"
+
+#include <wavpack/wavpack.h>
+#include <glib.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define ERRORLEN 80
+
+static constexpr Domain wavpack_domain("wavpack");
+
+/** 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(gcc_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(Decoder &decoder, WavpackContext *wpc, bool can_seek)
+{
+ bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
+ SampleFormat sample_format =
+ wavpack_bits_to_sample_format(is_float,
+ WavpackGetBytesPerSample(wpc));
+
+ Error error;
+ AudioFormat audio_format;
+ if (!audio_format_init_checked(audio_format,
+ WavpackGetSampleRate(wpc),
+ sample_format,
+ WavpackGetNumChannels(wpc), error)) {
+ LogError(error);
+ return;
+ }
+
+ const format_samples_t format_samples = is_float
+ ? format_samples_float
+ : format_samples_int;
+
+ const auto total_time =
+ SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc),
+ audio_format.sample_rate);
+
+ const int bytes_per_sample = WavpackGetBytesPerSample(wpc);
+ const int 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 = ARRAY_SIZE(chunk) /
+ audio_format.channels;
+
+ decoder_initialized(decoder, audio_format, can_seek, total_time);
+
+ DecoderCommand cmd = decoder_get_command(decoder);
+ while (cmd != DecoderCommand::STOP) {
+ if (cmd == DecoderCommand::SEEK) {
+ if (can_seek) {
+ auto where = decoder_seek_where_frame(decoder);
+
+ 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, nullptr, 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];
+ if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0)
+ return false;
+
+ *value_r = atof(buffer);
+ return true;
+}
+
+static bool
+wavpack_replaygain(ReplayGainInfo &rgi,
+ WavpackContext *wpc)
+{
+ rgi.Clear();
+
+ bool found = false;
+ found |= wavpack_tag_float(wpc, "replaygain_track_gain",
+ &rgi.tuples[REPLAY_GAIN_TRACK].gain);
+ found |= wavpack_tag_float(wpc, "replaygain_track_peak",
+ &rgi.tuples[REPLAY_GAIN_TRACK].peak);
+ found |= wavpack_tag_float(wpc, "replaygain_album_gain",
+ &rgi.tuples[REPLAY_GAIN_ALBUM].gain);
+ found |= wavpack_tag_float(wpc, "replaygain_album_peak",
+ &rgi.tuples[REPLAY_GAIN_ALBUM].peak);
+
+ return found;
+}
+
+static void
+wavpack_scan_tag_item(WavpackContext *wpc, const char *name,
+ TagType 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(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ char error[ERRORLEN];
+ WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error,
+ OPEN_TAGS, 0);
+ if (wpc == nullptr) {
+ FormatError(wavpack_domain,
+ "failed to open WavPack file \"%s\": %s",
+ path_fs.c_str(), error);
+ return false;
+ }
+
+ const auto duration =
+ SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc),
+ WavpackGetSampleRate(wpc));
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+
+ /* 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 != nullptr)
+ wavpack_scan_tag_item(wpc, name, (TagType)i,
+ handler, handler_ctx);
+ }
+
+ for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i)
+ wavpack_scan_tag_item(wpc, i->name, i->type,
+ handler, handler_ctx);
+
+ if (handler->pair != nullptr) {
+ 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 WavpackInput {
+ Decoder &decoder;
+ InputStream &is;
+ /* Needed for push_back_byte() */
+ int last_byte;
+
+ constexpr WavpackInput(Decoder &_decoder, InputStream &_is)
+ :decoder(_decoder), is(_is), last_byte(EOF) {}
+
+ int32_t ReadBytes(void *data, size_t bcount);
+};
+
+/**
+ * Little wrapper for struct WavpackInput to cast from void *.
+ */
+static WavpackInput *
+wpin(void *id)
+{
+ assert(id);
+ return (WavpackInput *)id;
+}
+
+static int32_t
+wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
+{
+ return wpin(id)->ReadBytes(data, bcount);
+}
+
+int32_t
+WavpackInput::ReadBytes(void *data, size_t bcount)
+{
+ uint8_t *buf = (uint8_t *)data;
+ int32_t i = 0;
+
+ if (last_byte != EOF) {
+ *buf++ = last_byte;
+ 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(&decoder, 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)
+{
+ WavpackInput &wpi = *wpin(id);
+
+ return wpi.is.GetOffset();
+}
+
+static int
+wavpack_input_set_pos_abs(void *id, uint32_t pos)
+{
+ WavpackInput &wpi = *wpin(id);
+
+ return wpi.is.LockSeek(pos, IgnoreError()) ? 0 : -1;
+}
+
+static int
+wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
+{
+ WavpackInput &wpi = *wpin(id);
+ InputStream &is = wpi.is;
+
+ offset_type offset = delta;
+ switch (mode) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is.KnownSize())
+ return -1;
+
+ offset += is.GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return is.LockSeek(offset, IgnoreError()) ? 0 : -1;
+}
+
+static int
+wavpack_input_push_back_byte(void *id, int c)
+{
+ WavpackInput &wpi = *wpin(id);
+
+ if (wpi.last_byte == EOF) {
+ wpi.last_byte = c;
+ return c;
+ } else {
+ return EOF;
+ }
+}
+
+static uint32_t
+wavpack_input_get_length(void *id)
+{
+ WavpackInput &wpi = *wpin(id);
+ InputStream &is = wpi.is;
+
+ if (!is.KnownSize())
+ return 0;
+
+ return is.GetSize();
+}
+
+static int
+wavpack_input_can_seek(void *id)
+{
+ WavpackInput &wpi = *wpin(id);
+ InputStream &is = wpi.is;
+
+ return is.IsSeekable();
+}
+
+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 WavpackInput *
+wavpack_open_wvc(Decoder &decoder, const char *uri)
+{
+ /*
+ * As we use dc->utf8url, this function will be bad for
+ * single files. utf8url is not absolute file path :/
+ */
+ if (uri == nullptr)
+ return nullptr;
+
+ char *wvc_url = g_strconcat(uri, "c", nullptr);
+
+ InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError());
+ g_free(wvc_url);
+
+ if (is_wvc == nullptr)
+ return nullptr;
+
+ return new WavpackInput(decoder, *is_wvc);
+}
+
+/*
+ * Decodes a stream.
+ */
+static void
+wavpack_streamdecode(Decoder &decoder, InputStream &is)
+{
+ int open_flags = OPEN_NORMALIZE;
+ bool can_seek = is.IsSeekable();
+
+ WavpackInput *wvc = wavpack_open_wvc(decoder, is.GetURI());
+ if (wvc != nullptr) {
+ open_flags |= OPEN_WVC;
+ can_seek &= wvc->is.IsSeekable();
+ }
+
+ if (!can_seek) {
+ open_flags |= OPEN_STREAMING;
+ }
+
+ WavpackInput isp(decoder, is);
+
+ char error[ERRORLEN];
+ WavpackContext *wpc =
+ WavpackOpenFileInputEx(&mpd_is_reader, &isp, wvc,
+ error, open_flags, 23);
+
+ if (wpc == nullptr) {
+ FormatError(wavpack_domain,
+ "failed to open WavPack stream: %s", error);
+ return;
+ }
+
+ wavpack_decode(decoder, wpc, can_seek);
+
+ WavpackCloseFile(wpc);
+
+ if (wvc != nullptr) {
+ delete &wvc->is;
+ delete wvc;
+ }
+}
+
+/*
+ * Decodes a file.
+ */
+static void
+wavpack_filedecode(Decoder &decoder, Path path_fs)
+{
+ char error[ERRORLEN];
+ WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error,
+ OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE,
+ 23);
+ if (wpc == nullptr) {
+ FormatWarning(wavpack_domain,
+ "failed to open WavPack file \"%s\": %s",
+ path_fs.c_str(), error);
+ return;
+ }
+
+ ReplayGainInfo rgi;
+ if (wavpack_replaygain(rgi, wpc))
+ decoder_replay_gain(decoder, &rgi);
+
+ wavpack_decode(decoder, wpc, true);
+
+ WavpackCloseFile(wpc);
+}
+
+static char const *const wavpack_suffixes[] = {
+ "wv",
+ nullptr
+};
+
+static char const *const wavpack_mime_types[] = {
+ "audio/x-wavpack",
+ nullptr
+};
+
+const struct DecoderPlugin 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/plugins/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx
new file mode 100644
index 000000000..2e5f9bd42
--- /dev/null
+++ b/src/decoder/plugins/WavpackDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_WAVPACK_HXX
+#define MPD_DECODER_WAVPACK_HXX
+
+extern const struct DecoderPlugin wavpack_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
new file mode 100644
index 000000000..fc58f0977
--- /dev/null
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "WildmidiDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "tag/TagHandler.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/Path.hxx"
+#include "system/FatalError.hxx"
+#include "Log.hxx"
+
+extern "C" {
+#include <wildmidi_lib.h>
+}
+
+static constexpr Domain wildmidi_domain("wildmidi");
+
+static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000;
+
+static bool
+wildmidi_init(const config_param &param)
+{
+ Error error;
+ const AllocatedPath path =
+ param.GetBlockPath("config_file",
+ "/etc/timidity/timidity.cfg",
+ error);
+ if (path.IsNull())
+ FatalError(error);
+
+ if (!FileExists(path)) {
+ const auto utf8 = path.ToUTF8();
+ FormatDebug(wildmidi_domain,
+ "configuration file does not exist: %s",
+ utf8.c_str());
+ return false;
+ }
+
+ return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0;
+}
+
+static void
+wildmidi_finish(void)
+{
+ WildMidi_Shutdown();
+}
+
+static void
+wildmidi_file_decode(Decoder &decoder, Path path_fs)
+{
+ static constexpr AudioFormat audio_format = {
+ WILDMIDI_SAMPLE_RATE,
+ SampleFormat::S16,
+ 2,
+ };
+ midi *wm;
+ const struct _WM_Info *info;
+
+ wm = WildMidi_Open(path_fs.c_str());
+ if (wm == nullptr)
+ return;
+
+ info = WildMidi_GetInfo(wm);
+ if (info == nullptr) {
+ WildMidi_Close(wm);
+ return;
+ }
+
+ const auto duration =
+ SongTime::FromScale<uint64_t>(info->approx_total_samples,
+ WILDMIDI_SAMPLE_RATE);
+
+ decoder_initialized(decoder, audio_format, true, duration);
+
+ DecoderCommand cmd;
+ 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 == DecoderCommand::SEEK) {
+ unsigned long seek_where =
+ decoder_seek_where_frame(decoder);
+
+ WildMidi_FastSeek(wm, &seek_where);
+ decoder_command_finished(decoder);
+ cmd = DecoderCommand::NONE;
+ }
+
+ } while (cmd == DecoderCommand::NONE);
+
+ WildMidi_Close(wm);
+}
+
+static bool
+wildmidi_scan_file(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ midi *wm = WildMidi_Open(path_fs.c_str());
+ if (wm == nullptr)
+ return false;
+
+ const struct _WM_Info *info = WildMidi_GetInfo(wm);
+ if (info == nullptr) {
+ WildMidi_Close(wm);
+ return false;
+ }
+
+ const auto duration =
+ SongTime::FromScale<uint64_t>(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 DecoderPlugin 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/plugins/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx
new file mode 100644
index 000000000..fc87aab80
--- /dev/null
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_WILDMIDI_HXX
+#define MPD_DECODER_WILDMIDI_HXX
+
+extern const struct DecoderPlugin wildmidi_decoder_plugin;
+
+#endif
diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx
new file mode 100644
index 000000000..11a0bcd42
--- /dev/null
+++ b/src/decoder/plugins/XiphTags.cxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* This File contains additional Tags for Xiph-Based Formats like Ogg-Vorbis,
+ * Flac and Opus which will be used in addition to the Tags in tag/TagNames.c
+ * see https://www.xiph.org/vorbis/doc/v-comment.html for further Info
+ */
+#include "config.h"
+#include "XiphTags.hxx"
+
+const struct tag_table xiph_tags[] = {
+ { "tracknumber", TAG_TRACK },
+ { "discnumber", TAG_DISC },
+ { "album artist", TAG_ALBUM_ARTIST },
+ { "description", TAG_COMMENT },
+ { nullptr, TAG_NUM_OF_ITEM_TYPES }
+};
diff --git a/src/decoder/plugins/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx
new file mode 100644
index 000000000..48a27425f
--- /dev/null
+++ b/src/decoder/plugins/XiphTags.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_XIPH_TAGS_HXX
+#define MPD_XIPH_TAGS_HXX
+
+#include "check.h"
+#include "tag/TagTable.hxx"
+
+extern const struct tag_table xiph_tags[];
+
+#endif
diff --git a/src/encoder/EncoderAPI.hxx b/src/encoder/EncoderAPI.hxx
new file mode 100644
index 000000000..b147eac21
--- /dev/null
+++ b/src/encoder/EncoderAPI.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This header is included by encoder plugins.
+ *
+ */
+
+#ifndef MPD_ENCODER_API_HXX
+#define MPD_ENCODER_API_HXX
+
+// IWYU pragma: begin_exports
+
+#include "EncoderPlugin.hxx"
+#include "AudioFormat.hxx"
+#include "tag/Tag.hxx"
+#include "config/ConfigData.hxx"
+
+// IWYU pragma: end_exports
+
+#endif
diff --git a/src/encoder/EncoderList.cxx b/src/encoder/EncoderList.cxx
new file mode 100644
index 000000000..4bca5a4fe
--- /dev/null
+++ b/src/encoder/EncoderList.cxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "EncoderList.hxx"
+#include "EncoderPlugin.hxx"
+#include "plugins/NullEncoderPlugin.hxx"
+#include "plugins/WaveEncoderPlugin.hxx"
+#include "plugins/VorbisEncoderPlugin.hxx"
+#include "plugins/OpusEncoderPlugin.hxx"
+#include "plugins/FlacEncoderPlugin.hxx"
+#include "plugins/ShineEncoderPlugin.hxx"
+#include "plugins/LameEncoderPlugin.hxx"
+#include "plugins/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
+#ifdef ENABLE_SHINE_ENCODER
+ &shine_encoder_plugin,
+#endif
+ nullptr
+};
+
+const EncoderPlugin *
+encoder_plugin_get(const char *name)
+{
+ encoder_plugins_for_each(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return nullptr;
+}
diff --git a/src/encoder/EncoderList.hxx b/src/encoder/EncoderList.hxx
new file mode 100644
index 000000000..e18d8ec74
--- /dev/null
+++ b/src/encoder/EncoderList.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_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) != nullptr; \
+ ++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 nullptr if none
+ * was found
+ */
+const EncoderPlugin *
+encoder_plugin_get(const char *name);
+
+#endif
diff --git a/src/encoder/EncoderPlugin.hxx b/src/encoder/EncoderPlugin.hxx
new file mode 100644
index 000000000..95e4e5838
--- /dev/null
+++ b/src/encoder/EncoderPlugin.hxx
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_PLUGIN_HXX
+#define MPD_ENCODER_PLUGIN_HXX
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+struct EncoderPlugin;
+struct AudioFormat;
+struct config_param;
+struct Tag;
+class Error;
+
+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,
+ Error &error);
+
+ void (*finish)(Encoder *encoder);
+
+ bool (*open)(Encoder *encoder,
+ AudioFormat &audio_format,
+ Error &error);
+
+ void (*close)(Encoder *encoder);
+
+ bool (*end)(Encoder *encoder, Error &error);
+
+ bool (*flush)(Encoder *encoder, Error &error);
+
+ bool (*pre_tag)(Encoder *encoder, Error &error);
+
+ bool (*tag)(Encoder *encoder, const Tag *tag,
+ Error &error);
+
+ bool (*write)(Encoder *encoder,
+ const void *data, size_t length,
+ Error &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 nullptr to ignore errors.
+ * @return an encoder object on success, nullptr on failure
+ */
+static inline Encoder *
+encoder_init(const EncoderPlugin &plugin, const config_param &param,
+ Error &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
+ * @return true on success
+ */
+static inline bool
+encoder_open(Encoder *encoder, AudioFormat &audio_format,
+ Error &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 != nullptr)
+ 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
+ * @return true on success
+ */
+static inline bool
+encoder_end(Encoder *encoder, Error &error)
+{
+ assert(encoder->open);
+ assert(!encoder->end);
+
+#ifndef NDEBUG
+ encoder->end = true;
+#endif
+
+ /* this method is optional */
+ return encoder->plugin.end != nullptr
+ ? 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
+ * @return true on success
+ */
+static inline bool
+encoder_flush(Encoder *encoder, Error &error)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag);
+ assert(!encoder->tag);
+ assert(!encoder->end);
+
+ /* this method is optional */
+ return encoder->plugin.flush != nullptr
+ ? 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
+ * @return true on success
+ */
+static inline bool
+encoder_pre_tag(Encoder *encoder, Error &error)
+{
+ assert(encoder->open);
+ assert(!encoder->pre_tag);
+ assert(!encoder->tag);
+ assert(!encoder->end);
+
+ /* this method is optional */
+ bool success = encoder->plugin.pre_tag != nullptr
+ ? 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
+ * @return true on success
+ */
+static inline bool
+encoder_tag(Encoder *encoder, const Tag *tag, Error &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 != nullptr
+ ? 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
+ * @return true on success
+ */
+static inline bool
+encoder_write(Encoder *encoder, const void *data, size_t length,
+ Error &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, nullptr on failure
+ */
+static inline const char *
+encoder_get_mime_type(Encoder *encoder)
+{
+ /* this method is optional */
+ return encoder->plugin.get_mime_type != nullptr
+ ? encoder->plugin.get_mime_type(encoder)
+ : nullptr;
+}
+
+#endif
diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx
deleted file mode 100644
index fa7ed992d..000000000
--- a/src/encoder/FlacEncoderPlugin.cxx
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FlacEncoderPlugin.hxx"
-#include "EncoderAPI.hxx"
-#include "AudioFormat.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/fifo_buffer.h"
-
-extern "C" {
-#include "util/growing_fifo.h"
-}
-
-#include <assert.h>
-#include <string.h>
-
-#include <FLAC/stream_encoder.h>
-
-#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 constexpr Domain flac_encoder_domain("vorbis_encoder");
-
-static bool
-flac_encoder_configure(struct flac_encoder *encoder, const config_param &param,
- gcc_unused Error &error)
-{
- encoder->compression = param.GetBlockValue("compression", 5u);
-
- return true;
-}
-
-static Encoder *
-flac_encoder_init(const config_param &param, Error &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,
- Error &error)
-{
- if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
- encoder->compression)) {
- error.Format(config_domain,
- "error setting flac compression to %d",
- encoder->compression);
- return false;
- }
-
- if ( !FLAC__stream_encoder_set_channels(encoder->fse,
- encoder->audio_format.channels)) {
- error.Format(config_domain,
- "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)) {
- error.Format(config_domain,
- "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)) {
- error.Format(config_domain,
- "error setting flac sample rate to %d",
- encoder->audio_format.sample_rate);
- return false;
- }
- return true;
-}
-
-static FLAC__StreamEncoderWriteStatus
-flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse,
- const FLAC__byte data[],
- size_t bytes,
- gcc_unused unsigned samples,
- gcc_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, Error &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) {
- error.Set(flac_encoder_domain, "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) {
- error.Format(flac_encoder_domain,
- "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, gcc_unused Error &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,
- gcc_unused Error &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)) {
- error.Set(flac_encoder_domain, "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(gcc_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
deleted file mode 100644
index 928a7f93e..000000000
--- a/src/encoder/FlacEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 06082d16b..000000000
--- a/src/encoder/LameEncoderPlugin.cxx
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "LameEncoderPlugin.hxx"
-#include "EncoderAPI.hxx"
-#include "AudioFormat.hxx"
-#include "ConfigError.hxx"
-#include "util/NumberParser.hxx"
-#include "util/ReusableArray.hxx"
-#include "util/Manual.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <lame/lame.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct LameEncoder final {
- Encoder encoder;
-
- AudioFormat audio_format;
- float quality;
- int bitrate;
-
- lame_global_flags *gfp;
-
- Manual<ReusableArray<unsigned char, 32768>> output_buffer;
- unsigned char *output_begin, *output_end;
-
- LameEncoder():encoder(lame_encoder_plugin) {}
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain lame_encoder_domain("lame_encoder");
-
-bool
-LameEncoder::Configure(const config_param &param, Error &error)
-{
- const char *value;
- char *endptr;
-
- value = param.GetBlockValue("quality");
- if (value != nullptr) {
- /* a quality was configured (VBR) */
-
- quality = ParseDouble(value, &endptr);
-
- if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
- error.Format(config_domain,
- "quality \"%s\" is not a number in the "
- "range -1 to 10",
- value);
- return false;
- }
-
- if (param.GetBlockValue("bitrate") != nullptr) {
- error.Set(config_domain,
- "quality and bitrate are both defined");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- value = param.GetBlockValue("bitrate");
- if (value == nullptr) {
- error.Set(config_domain,
- "neither bitrate nor quality defined");
- return false;
- }
-
- quality = -2.0;
- bitrate = ParseInt(value, &endptr);
-
- if (*endptr != '\0' || bitrate <= 0) {
- error.Set(config_domain,
- "bitrate should be a positive integer");
- return false;
- }
- }
-
- return true;
-}
-
-static Encoder *
-lame_encoder_init(const config_param &param, Error &error)
-{
- LameEncoder *encoder = new LameEncoder();
-
- /* load configuration from "param" */
- if (!encoder->Configure(param, error)) {
- /* configuration has failed, roll back and return error */
- delete encoder;
- return nullptr;
- }
-
- return &encoder->encoder;
-}
-
-static void
-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 */
- delete encoder;
-}
-
-static bool
-lame_encoder_setup(LameEncoder *encoder, Error &error)
-{
- if (encoder->quality >= -1.0) {
- /* a quality was configured (VBR) */
-
- if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) {
- error.Set(lame_encoder_domain,
- "error setting lame VBR mode");
- return false;
- }
- if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) {
- error.Set(lame_encoder_domain,
- "error setting lame VBR quality");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) {
- error.Set(lame_encoder_domain,
- "error setting lame bitrate");
- return false;
- }
- }
-
- if (0 != lame_set_num_channels(encoder->gfp,
- encoder->audio_format.channels)) {
- error.Set(lame_encoder_domain,
- "error setting lame num channels");
- return false;
- }
-
- if (0 != lame_set_in_samplerate(encoder->gfp,
- encoder->audio_format.sample_rate)) {
- error.Set(lame_encoder_domain,
- "error setting lame sample rate");
- return false;
- }
-
- if (0 != lame_set_out_samplerate(encoder->gfp,
- encoder->audio_format.sample_rate)) {
- error.Set(lame_encoder_domain,
- "error setting lame out sample rate");
- return false;
- }
-
- if (0 > lame_init_params(encoder->gfp)) {
- error.Set(lame_encoder_domain,
- "error initializing lame params");
- return false;
- }
-
- return true;
-}
-
-static bool
-lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &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) {
- error.Set(lame_encoder_domain, "lame_init() failed");
- return false;
- }
-
- if (!lame_encoder_setup(encoder, error)) {
- lame_close(encoder->gfp);
- return false;
- }
-
- encoder->output_buffer.Construct();
- encoder->output_begin = encoder->output_end = nullptr;
-
- return true;
-}
-
-static void
-lame_encoder_close(Encoder *_encoder)
-{
- LameEncoder *encoder = (LameEncoder *)_encoder;
-
- lame_close(encoder->gfp);
- encoder->output_buffer.Destruct();
-}
-
-static bool
-lame_encoder_write(Encoder *_encoder,
- const void *data, size_t length,
- gcc_unused Error &error)
-{
- LameEncoder *encoder = (LameEncoder *)_encoder;
- const int16_t *src = (const int16_t*)data;
-
- assert(encoder->output_begin == encoder->output_end);
-
- const unsigned num_frames =
- length / encoder->audio_format.GetFrameSize();
- const unsigned num_samples =
- length / encoder->audio_format.GetSampleSize();
-
- /* worst-case formula according to LAME documentation */
- const size_t output_buffer_size = 5 * num_samples / 4 + 7200;
- const auto output_buffer = encoder->output_buffer->Get(output_buffer_size);
-
- /* this is for only 16-bit audio */
-
- int bytes_out = lame_encode_buffer_interleaved(encoder->gfp,
- const_cast<short *>(src),
- num_frames,
- output_buffer,
- output_buffer_size);
-
- if (bytes_out < 0) {
- error.Set(lame_encoder_domain, "lame encoder failed");
- return false;
- }
-
- encoder->output_begin = output_buffer;
- encoder->output_end = output_buffer + bytes_out;
- return true;
-}
-
-static size_t
-lame_encoder_read(Encoder *_encoder, void *dest, size_t length)
-{
- LameEncoder *encoder = (LameEncoder *)_encoder;
-
- const auto begin = encoder->output_begin;
- assert(begin <= encoder->output_end);
- const size_t remainning = encoder->output_end - begin;
- if (length > remainning)
- length = remainning;
-
- memcpy(dest, begin, length);
-
- encoder->output_begin = begin + 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
deleted file mode 100644
index 49832baee..000000000
--- a/src/encoder/LameEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 3b1aae5e2..000000000
--- a/src/encoder/NullEncoderPlugin.cxx
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "NullEncoderPlugin.hxx"
-#include "EncoderAPI.hxx"
-#include "util/fifo_buffer.h"
-extern "C" {
-#include "util/growing_fifo.h"
-}
-#include "Compiler.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 Error &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 Error &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 Error &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
deleted file mode 100644
index b741a2f6d..000000000
--- a/src/encoder/NullEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ENCODER_NULL_HXX
-#define MPD_ENCODER_NULL_HXX
-
-extern const struct EncoderPlugin null_encoder_plugin;
-
-#endif
diff --git a/src/encoder/OggSerial.cxx b/src/encoder/OggSerial.cxx
deleted file mode 100644
index 0d4fc5a9f..000000000
--- a/src/encoder/OggSerial.cxx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "OggSerial.hxx"
-#include "system/Clock.hxx"
-#include "Compiler.h"
-
-#include <atomic>
-
-static std::atomic_uint next_ogg_serial;
-
-int
-GenerateOggSerial()
-{
- unsigned serial = ++next_ogg_serial;
- if (gcc_unlikely(serial < 16)) {
- /* first-time initialization: seed with a clock value,
- which is random enough for our use */
-
- /* this code is not race-free, but good enough */
- const unsigned seed = MonotonicClockMS();
- next_ogg_serial = serial = seed;
- }
-
- return serial;
-}
-
diff --git a/src/encoder/OggSerial.hxx b/src/encoder/OggSerial.hxx
deleted file mode 100644
index 2e5a020c6..000000000
--- a/src/encoder/OggSerial.hxx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OGG_SERIAL_HXX
-#define MPD_OGG_SERIAL_HXX
-
-/**
- * Generate the next pseudo-random Ogg serial.
- */
-int
-GenerateOggSerial();
-
-#endif
diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx
deleted file mode 100644
index e8dcdf970..000000000
--- a/src/encoder/OggStream.hxx
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 243dc0836..000000000
--- a/src/encoder/OpusEncoderPlugin.cxx
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OpusEncoderPlugin.hxx"
-#include "OggStream.hxx"
-#include "OggSerial.hxx"
-#include "EncoderAPI.hxx"
-#include "AudioFormat.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/ByteOrder.hxx"
-
-#include <opus.h>
-#include <ogg/ogg.h>
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-
-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) {}
-};
-
-static constexpr Domain opus_encoder_domain("opus_encoder");
-
-static bool
-opus_encoder_configure(struct opus_encoder *encoder,
- const config_param &param, Error &error)
-{
- 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) {
- error.Set(config_domain, "Invalid bit rate");
- return false;
- }
- }
-
- encoder->complexity = param.GetBlockValue("complexity", 10u);
- if (encoder->complexity > 10) {
- error.Format(config_domain, "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 {
- error.Format(config_domain, "Invalid signal");
- return false;
- }
-
- return true;
-}
-
-static Encoder *
-opus_encoder_init(const config_param &param, Error &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,
- Error &error)
-{
- 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_code;
- encoder->enc = opus_encoder_create(audio_format.sample_rate,
- audio_format.channels,
- OPUS_APPLICATION_AUDIO,
- &error_code);
- if (encoder->enc == nullptr) {
- error.Set(opus_encoder_domain, error_code,
- opus_strerror(error_code));
- 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(GenerateOggSerial());
- 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,
- Error &error)
-{
- 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) {
- error.Set(opus_encoder_domain, "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, Error &error)
-{
- 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);
-}
-
-static bool
-opus_encoder_flush(Encoder *_encoder, gcc_unused Error &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,
- Error &error)
-{
- 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))
- return false;
- }
-
- return true;
-}
-
-static bool
-opus_encoder_write(Encoder *_encoder,
- const void *_data, size_t length,
- Error &error)
-{
- 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))
- 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))
- 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) = ToLE16(encoder->lookahead);
- *(uint32_t *)(header + 12) =
- ToLE32(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) = ToLE32(version_length);
- memcpy(comments + 12, version, version_length);
- *(uint32_t *)(comments + 12 + version_length) = ToLE32(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(gcc_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
deleted file mode 100644
index 3bb55e051..000000000
--- a/src/encoder/OpusEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 543e71d64..000000000
--- a/src/encoder/TwolameEncoderPlugin.cxx
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "TwolameEncoderPlugin.hxx"
-#include "EncoderAPI.hxx"
-#include "AudioFormat.hxx"
-#include "ConfigError.hxx"
-#include "util/NumberParser.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <twolame.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct TwolameEncoder final {
- Encoder encoder;
-
- AudioFormat audio_format;
- float quality;
- int bitrate;
-
- twolame_options *options;
-
- unsigned char output_buffer[32768];
- size_t output_buffer_length;
- size_t output_buffer_position;
-
- /**
- * Call libtwolame's flush function when the output_buffer is
- * empty?
- */
- bool flush;
-
- TwolameEncoder():encoder(twolame_encoder_plugin) {}
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain twolame_encoder_domain("twolame_encoder");
-
-bool
-TwolameEncoder::Configure(const config_param &param, Error &error)
-{
- const char *value;
- char *endptr;
-
- value = param.GetBlockValue("quality");
- if (value != nullptr) {
- /* a quality was configured (VBR) */
-
- quality = ParseDouble(value, &endptr);
-
- if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
- error.Format(config_domain,
- "quality \"%s\" is not a number in the "
- "range -1 to 10",
- value);
- return false;
- }
-
- if (param.GetBlockValue("bitrate") != nullptr) {
- error.Set(config_domain,
- "quality and bitrate are both defined");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- value = param.GetBlockValue("bitrate");
- if (value == nullptr) {
- error.Set(config_domain,
- "neither bitrate nor quality defined");
- return false;
- }
-
- quality = -2.0;
- bitrate = ParseInt(value, &endptr);
-
- if (*endptr != '\0' || bitrate <= 0) {
- error.Set(config_domain,
- "bitrate should be a positive integer");
- return false;
- }
- }
-
- return true;
-}
-
-static Encoder *
-twolame_encoder_init(const config_param &param, Error &error_r)
-{
- FormatDebug(twolame_encoder_domain,
- "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, Error &error)
-{
- if (encoder->quality >= -1.0) {
- /* a quality was configured (VBR) */
-
- if (0 != twolame_set_VBR(encoder->options, true)) {
- error.Set(twolame_encoder_domain,
- "error setting twolame VBR mode");
- return false;
- }
- if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
- error.Set(twolame_encoder_domain,
- "error setting twolame VBR quality");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
- error.Set(twolame_encoder_domain,
- "error setting twolame bitrate");
- return false;
- }
- }
-
- if (0 != twolame_set_num_channels(encoder->options,
- encoder->audio_format.channels)) {
- error.Set(twolame_encoder_domain,
- "error setting twolame num channels");
- return false;
- }
-
- if (0 != twolame_set_in_samplerate(encoder->options,
- encoder->audio_format.sample_rate)) {
- error.Set(twolame_encoder_domain,
- "error setting twolame sample rate");
- return false;
- }
-
- if (0 > twolame_init_params(encoder->options)) {
- error.Set(twolame_encoder_domain,
- "error initializing twolame params");
- return false;
- }
-
- return true;
-}
-
-static bool
-twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
- Error &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) {
- error.Set(twolame_encoder_domain, "twolame_init() failed");
- return false;
- }
-
- if (!twolame_encoder_setup(encoder, error)) {
- twolame_close(&encoder->options);
- return false;
- }
-
- encoder->output_buffer_length = 0;
- encoder->output_buffer_position = 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 Error &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 Error &error)
-{
- TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
- const int16_t *src = (const int16_t*)data;
-
- assert(encoder->output_buffer_position ==
- encoder->output_buffer_length);
-
- const unsigned num_frames =
- length / encoder->audio_format.GetFrameSize();
-
- int bytes_out = twolame_encode_buffer_interleaved(encoder->options,
- src, num_frames,
- encoder->output_buffer,
- sizeof(encoder->output_buffer));
- if (bytes_out < 0) {
- error.Set(twolame_encoder_domain, "twolame encoder failed");
- return false;
- }
-
- encoder->output_buffer_length = (size_t)bytes_out;
- encoder->output_buffer_position = 0;
- return true;
-}
-
-static size_t
-twolame_encoder_read(Encoder *_encoder, void *dest, size_t length)
-{
- TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
-
- assert(encoder->output_buffer_position <=
- encoder->output_buffer_length);
-
- if (encoder->output_buffer_position == encoder->output_buffer_length &&
- encoder->flush) {
- int ret = twolame_encode_flush(encoder->options,
- encoder->output_buffer,
- sizeof(encoder->output_buffer));
- if (ret > 0) {
- encoder->output_buffer_length = (size_t)ret;
- encoder->output_buffer_position = 0;
- }
-
- encoder->flush = false;
- }
-
-
- const size_t remainning = encoder->output_buffer_length
- - encoder->output_buffer_position;
- if (length > remainning)
- length = remainning;
-
- memcpy(dest, encoder->output_buffer + encoder->output_buffer_position,
- length);
-
- encoder->output_buffer_position += 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
deleted file mode 100644
index dd8a536f6..000000000
--- a/src/encoder/TwolameEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 5b40aaea1..000000000
--- a/src/encoder/VorbisEncoderPlugin.cxx
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "VorbisEncoderPlugin.hxx"
-#include "OggStream.hxx"
-#include "OggSerial.hxx"
-#include "EncoderAPI.hxx"
-#include "tag/Tag.hxx"
-#include "AudioFormat.hxx"
-#include "ConfigError.hxx"
-#include "util/NumberParser.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <vorbis/vorbisenc.h>
-
-#include <glib.h>
-
-#include <assert.h>
-
-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 constexpr Domain vorbis_encoder_domain("vorbis_encoder");
-
-static bool
-vorbis_encoder_configure(struct vorbis_encoder *encoder,
- const config_param &param, Error &error)
-{
- const char *value = param.GetBlockValue("quality");
- if (value != nullptr) {
- /* a quality was configured (VBR) */
-
- char *endptr;
- encoder->quality = ParseDouble(value, &endptr);
-
- if (*endptr != '\0' || encoder->quality < -1.0 ||
- encoder->quality > 10.0) {
- error.Format(config_domain,
- "quality \"%s\" is not a number in the "
- "range -1 to 10",
- value);
- return false;
- }
-
- if (param.GetBlockValue("bitrate") != nullptr) {
- error.Set(config_domain,
- "quality and bitrate are both defined");
- return false;
- }
- } else {
- /* a bit rate was configured */
-
- value = param.GetBlockValue("bitrate");
- if (value == nullptr) {
- error.Set(config_domain,
- "neither bitrate nor quality defined");
- return false;
- }
-
- encoder->quality = -2.0;
-
- char *endptr;
- encoder->bitrate = ParseInt(value, &endptr);
- if (*endptr != '\0' || encoder->bitrate <= 0) {
- error.Set(config_domain,
- "bitrate should be a positive integer");
- return false;
- }
- }
-
- return true;
-}
-
-static Encoder *
-vorbis_encoder_init(const config_param &param, Error &error)
-{
- vorbis_encoder *encoder = new vorbis_encoder();
-
- /* load configuration from "param" */
- if (!vorbis_encoder_configure(encoder, param, error)) {
- /* 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, Error &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)) {
- error.Set(vorbis_encoder_domain,
- "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)) {
- error.Set(vorbis_encoder_domain,
- "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(GenerateOggSerial());
-
- 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,
- Error &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, gcc_unused Error &error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- encoder->stream.Flush();
- return true;
-}
-
-static bool
-vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error)
-{
- struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
-
- 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,
- gcc_unused Error &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(GenerateOggSerial());
-
- /* 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,
- gcc_unused Error &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(gcc_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
deleted file mode 100644
index d5d6125d2..000000000
--- a/src/encoder/VorbisEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index acae0be9e..000000000
--- a/src/encoder/WaveEncoderPlugin.cxx
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "WaveEncoderPlugin.hxx"
-#include "EncoderAPI.hxx"
-#include "system/ByteOrder.hxx"
-#include "util/fifo_buffer.h"
-extern "C" {
-#include "util/growing_fifo.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 = ToLE32(0x46464952);
- header->id_wave = ToLE32(0x45564157);
- header->id_fmt = ToLE32(0x20746d66);
- header->id_data = ToLE32(0x61746164);
-
- /* wave format */
- header->format = ToLE16(1); // PCM_FORMAT
- header->channels = ToLE16(channels);
- header->bits = ToLE16(bits);
- header->freq = ToLE32(freq);
- header->blocksize = ToLE16(block_size);
- header->byterate = ToLE32(freq * block_size);
-
- /* chunk sizes (fake data length) */
- header->fmt_size = ToLE32(16);
- header->data_size = ToLE32(data_size);
- header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
-}
-
-static Encoder *
-wave_encoder_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- WaveEncoder *encoder = new WaveEncoder();
- return &encoder->encoder;
-}
-
-static void
-wave_encoder_finish(Encoder *_encoder)
-{
- WaveEncoder *encoder = (WaveEncoder *)_encoder;
-
- delete encoder;
-}
-
-static bool
-wave_encoder_open(Encoder *_encoder,
- AudioFormat &audio_format,
- gcc_unused Error &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 size_t
-pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
-{
- size_t cnt = length >> 1;
- while (cnt > 0) {
- *dst16++ = ToLE16(*src16++);
- cnt--;
- }
- return length;
-}
-
-static size_t
-pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length)
-{
- size_t cnt = length >> 2;
- while (cnt > 0){
- *dst32++ = ToLE32(*src32++);
- cnt--;
- }
- return length;
-}
-
-static 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 Error &error)
-{
- WaveEncoder *encoder = (WaveEncoder *)_encoder;
-
- uint8_t *dst = (uint8_t *)growing_fifo_write(&encoder->buffer, length);
-
- if (IsLittleEndian()) {
- 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;
- }
- } else {
- switch (encoder->bits) {
- case 8:
- memcpy(dst, src, length);
- break;
- case 16:
- length = pcm16_to_wave((uint16_t *)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((uint32_t *)dst,
- (const uint32_t *)src, length);
- break;
- }
- }
-
- 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
deleted file mode 100644
index 190ee131e..000000000
--- a/src/encoder/WaveEncoderPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ENCODER_WAVE_HXX
-#define MPD_ENCODER_WAVE_HXX
-
-extern const struct EncoderPlugin wave_encoder_plugin;
-
-#endif
diff --git a/src/encoder/plugins/FlacEncoderPlugin.cxx b/src/encoder/plugins/FlacEncoderPlugin.cxx
new file mode 100644
index 000000000..26987fe99
--- /dev/null
+++ b/src/encoder/plugins/FlacEncoderPlugin.cxx
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FlacEncoderPlugin.hxx"
+#include "../EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Manual.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#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().
+ */
+ Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
+
+ flac_encoder():encoder(flac_encoder_plugin) {}
+};
+
+static constexpr Domain flac_encoder_domain("vorbis_encoder");
+
+static bool
+flac_encoder_configure(struct flac_encoder *encoder, const config_param &param,
+ gcc_unused Error &error)
+{
+ encoder->compression = param.GetBlockValue("compression", 5u);
+
+ return true;
+}
+
+static Encoder *
+flac_encoder_init(const config_param &param, Error &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,
+ Error &error)
+{
+ if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
+ encoder->compression)) {
+ error.Format(config_domain,
+ "error setting flac compression to %d",
+ encoder->compression);
+ return false;
+ }
+
+ if ( !FLAC__stream_encoder_set_channels(encoder->fse,
+ encoder->audio_format.channels)) {
+ error.Format(config_domain,
+ "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)) {
+ error.Format(config_domain,
+ "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)) {
+ error.Format(config_domain,
+ "error setting flac sample rate to %d",
+ encoder->audio_format.sample_rate);
+ return false;
+ }
+ return true;
+}
+
+static FLAC__StreamEncoderWriteStatus
+flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse,
+ const FLAC__byte data[],
+ size_t bytes,
+ gcc_unused unsigned samples,
+ gcc_unused unsigned current_frame, void *client_data)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *) client_data;
+
+ //transfer data to buffer
+ encoder->output_buffer->Append((const uint8_t *)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();
+ encoder->output_buffer.Destruct();
+}
+
+static bool
+flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &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) {
+ error.Set(flac_encoder_domain, "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.Construct(8192);
+
+ /* 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) {
+ error.Format(flac_encoder_domain,
+ "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, gcc_unused Error &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,
+ gcc_unused Error &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)) {
+ error.Set(flac_encoder_domain, "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;
+
+ return encoder->output_buffer->Read((uint8_t *)dest, length);
+}
+
+static const char *
+flac_encoder_get_mime_type(gcc_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/plugins/FlacEncoderPlugin.hxx b/src/encoder/plugins/FlacEncoderPlugin.hxx
new file mode 100644
index 000000000..0cdc01600
--- /dev/null
+++ b/src/encoder/plugins/FlacEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_FLAC_HXX
+#define MPD_ENCODER_FLAC_HXX
+
+extern const struct EncoderPlugin flac_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/LameEncoderPlugin.cxx b/src/encoder/plugins/LameEncoderPlugin.cxx
new file mode 100644
index 000000000..3878b52bb
--- /dev/null
+++ b/src/encoder/plugins/LameEncoderPlugin.cxx
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LameEncoderPlugin.hxx"
+#include "../EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigError.hxx"
+#include "util/NumberParser.hxx"
+#include "util/ReusableArray.hxx"
+#include "util/Manual.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <lame/lame.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct LameEncoder final {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+ float quality;
+ int bitrate;
+
+ lame_global_flags *gfp;
+
+ Manual<ReusableArray<unsigned char, 32768>> output_buffer;
+ unsigned char *output_begin, *output_end;
+
+ LameEncoder():encoder(lame_encoder_plugin) {}
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain lame_encoder_domain("lame_encoder");
+
+bool
+LameEncoder::Configure(const config_param &param, Error &error)
+{
+ const char *value;
+ char *endptr;
+
+ value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ /* a quality was configured (VBR) */
+
+ quality = ParseDouble(value, &endptr);
+
+ if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
+ error.Format(config_domain,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10",
+ value);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ error.Set(config_domain,
+ "quality and bitrate are both defined");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ error.Set(config_domain,
+ "neither bitrate nor quality defined");
+ return false;
+ }
+
+ quality = -2.0;
+ bitrate = ParseInt(value, &endptr);
+
+ if (*endptr != '\0' || bitrate <= 0) {
+ error.Set(config_domain,
+ "bitrate should be a positive integer");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Encoder *
+lame_encoder_init(const config_param &param, Error &error)
+{
+ LameEncoder *encoder = new LameEncoder();
+
+ /* load configuration from "param" */
+ if (!encoder->Configure(param, error)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+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 */
+ delete encoder;
+}
+
+static bool
+lame_encoder_setup(LameEncoder *encoder, Error &error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != lame_set_VBR(encoder->gfp, vbr_rh)) {
+ error.Set(lame_encoder_domain,
+ "error setting lame VBR mode");
+ return false;
+ }
+ if (0 != lame_set_VBR_q(encoder->gfp, encoder->quality)) {
+ error.Set(lame_encoder_domain,
+ "error setting lame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != lame_set_brate(encoder->gfp, encoder->bitrate)) {
+ error.Set(lame_encoder_domain,
+ "error setting lame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != lame_set_num_channels(encoder->gfp,
+ encoder->audio_format.channels)) {
+ error.Set(lame_encoder_domain,
+ "error setting lame num channels");
+ return false;
+ }
+
+ if (0 != lame_set_in_samplerate(encoder->gfp,
+ encoder->audio_format.sample_rate)) {
+ error.Set(lame_encoder_domain,
+ "error setting lame sample rate");
+ return false;
+ }
+
+ if (0 != lame_set_out_samplerate(encoder->gfp,
+ encoder->audio_format.sample_rate)) {
+ error.Set(lame_encoder_domain,
+ "error setting lame out sample rate");
+ return false;
+ }
+
+ if (0 > lame_init_params(encoder->gfp)) {
+ error.Set(lame_encoder_domain,
+ "error initializing lame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+lame_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &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) {
+ error.Set(lame_encoder_domain, "lame_init() failed");
+ return false;
+ }
+
+ if (!lame_encoder_setup(encoder, error)) {
+ lame_close(encoder->gfp);
+ return false;
+ }
+
+ encoder->output_buffer.Construct();
+ encoder->output_begin = encoder->output_end = nullptr;
+
+ return true;
+}
+
+static void
+lame_encoder_close(Encoder *_encoder)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+
+ lame_close(encoder->gfp);
+ encoder->output_buffer.Destruct();
+}
+
+static bool
+lame_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ gcc_unused Error &error)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+ const int16_t *src = (const int16_t*)data;
+
+ assert(encoder->output_begin == encoder->output_end);
+
+ const unsigned num_frames =
+ length / encoder->audio_format.GetFrameSize();
+ const unsigned num_samples =
+ length / encoder->audio_format.GetSampleSize();
+
+ /* worst-case formula according to LAME documentation */
+ const size_t output_buffer_size = 5 * num_samples / 4 + 7200;
+ const auto output_buffer = encoder->output_buffer->Get(output_buffer_size);
+
+ /* this is for only 16-bit audio */
+
+ int bytes_out = lame_encode_buffer_interleaved(encoder->gfp,
+ const_cast<short *>(src),
+ num_frames,
+ output_buffer,
+ output_buffer_size);
+
+ if (bytes_out < 0) {
+ error.Set(lame_encoder_domain, "lame encoder failed");
+ return false;
+ }
+
+ encoder->output_begin = output_buffer;
+ encoder->output_end = output_buffer + bytes_out;
+ return true;
+}
+
+static size_t
+lame_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ LameEncoder *encoder = (LameEncoder *)_encoder;
+
+ const auto begin = encoder->output_begin;
+ assert(begin <= encoder->output_end);
+ const size_t remainning = encoder->output_end - begin;
+ if (length > remainning)
+ length = remainning;
+
+ memcpy(dest, begin, length);
+
+ encoder->output_begin = begin + 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/plugins/LameEncoderPlugin.hxx b/src/encoder/plugins/LameEncoderPlugin.hxx
new file mode 100644
index 000000000..03e398f67
--- /dev/null
+++ b/src/encoder/plugins/LameEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_LAME_HXX
+#define MPD_ENCODER_LAME_HXX
+
+extern const struct EncoderPlugin lame_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/NullEncoderPlugin.cxx b/src/encoder/plugins/NullEncoderPlugin.cxx
new file mode 100644
index 000000000..1d571d465
--- /dev/null
+++ b/src/encoder/plugins/NullEncoderPlugin.cxx
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NullEncoderPlugin.hxx"
+#include "../EncoderAPI.hxx"
+#include "util/Manual.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+
+struct NullEncoder final {
+ Encoder encoder;
+
+ Manual<DynamicFifoBuffer<uint8_t>> buffer;
+
+ NullEncoder()
+ :encoder(null_encoder_plugin) {}
+};
+
+static Encoder *
+null_encoder_init(gcc_unused const config_param &param,
+ gcc_unused Error &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;
+
+ encoder->buffer.Destruct();
+}
+
+
+static bool
+null_encoder_open(Encoder *_encoder,
+ gcc_unused AudioFormat &audio_format,
+ gcc_unused Error &error)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+ encoder->buffer.Construct(8192);
+ return true;
+}
+
+static bool
+null_encoder_write(Encoder *_encoder,
+ const void *data, size_t length,
+ gcc_unused Error &error)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+
+ encoder->buffer->Append((const uint8_t *)data, length);
+ return length;
+}
+
+static size_t
+null_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ NullEncoder *encoder = (NullEncoder *)_encoder;
+
+ return encoder->buffer->Read((uint8_t *)dest, 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/plugins/NullEncoderPlugin.hxx b/src/encoder/plugins/NullEncoderPlugin.hxx
new file mode 100644
index 000000000..6acf88e49
--- /dev/null
+++ b/src/encoder/plugins/NullEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_NULL_HXX
+#define MPD_ENCODER_NULL_HXX
+
+extern const struct EncoderPlugin null_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/OggSerial.cxx b/src/encoder/plugins/OggSerial.cxx
new file mode 100644
index 000000000..677829439
--- /dev/null
+++ b/src/encoder/plugins/OggSerial.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "OggSerial.hxx"
+#include "system/Clock.hxx"
+#include "Compiler.h"
+
+#include <atomic>
+
+static std::atomic_uint next_ogg_serial;
+
+int
+GenerateOggSerial()
+{
+ unsigned serial = ++next_ogg_serial;
+ if (gcc_unlikely(serial < 16)) {
+ /* first-time initialization: seed with a clock value,
+ which is random enough for our use */
+
+ /* this code is not race-free, but good enough */
+ const unsigned seed = MonotonicClockMS();
+ next_ogg_serial = serial = seed;
+ }
+
+ return serial;
+}
+
diff --git a/src/encoder/plugins/OggSerial.hxx b/src/encoder/plugins/OggSerial.hxx
new file mode 100644
index 000000000..ceba8ebf9
--- /dev/null
+++ b/src/encoder/plugins/OggSerial.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OGG_SERIAL_HXX
+#define MPD_OGG_SERIAL_HXX
+
+/**
+ * Generate the next pseudo-random Ogg serial.
+ */
+int
+GenerateOggSerial();
+
+#endif
diff --git a/src/encoder/plugins/OggStream.hxx b/src/encoder/plugins/OggStream.hxx
new file mode 100644
index 000000000..805238c1d
--- /dev/null
+++ b/src/encoder/plugins/OggStream.hxx
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/plugins/OpusEncoderPlugin.cxx b/src/encoder/plugins/OpusEncoderPlugin.cxx
new file mode 100644
index 000000000..27b614b86
--- /dev/null
+++ b/src/encoder/plugins/OpusEncoderPlugin.cxx
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpusEncoderPlugin.hxx"
+#include "OggStream.hxx"
+#include "OggSerial.hxx"
+#include "../EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Alloc.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+
+#include <opus.h>
+#include <ogg/ogg.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+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) {}
+};
+
+static constexpr Domain opus_encoder_domain("opus_encoder");
+
+static bool
+opus_encoder_configure(struct opus_encoder *encoder,
+ const config_param &param, Error &error)
+{
+ 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) {
+ error.Set(config_domain, "Invalid bit rate");
+ return false;
+ }
+ }
+
+ encoder->complexity = param.GetBlockValue("complexity", 10u);
+ if (encoder->complexity > 10) {
+ error.Format(config_domain, "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 {
+ error.Format(config_domain, "Invalid signal");
+ return false;
+ }
+
+ return true;
+}
+
+static Encoder *
+opus_encoder_init(const config_param &param, Error &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 nullptr;
+ }
+
+ 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,
+ Error &error)
+{
+ 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_code;
+ encoder->enc = opus_encoder_create(audio_format.sample_rate,
+ audio_format.channels,
+ OPUS_APPLICATION_AUDIO,
+ &error_code);
+ if (encoder->enc == nullptr) {
+ error.Set(opus_encoder_domain, error_code,
+ opus_strerror(error_code));
+ 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 *)xalloc(encoder->buffer_size);
+
+ encoder->stream.Initialize(GenerateOggSerial());
+ encoder->packetno = 0;
+
+ return true;
+}
+
+static void
+opus_encoder_close(Encoder *_encoder)
+{
+ struct opus_encoder *encoder = (struct opus_encoder *)_encoder;
+
+ encoder->stream.Deinitialize();
+ free(encoder->buffer);
+ opus_encoder_destroy(encoder->enc);
+}
+
+static bool
+opus_encoder_do_encode(struct opus_encoder *encoder, bool eos,
+ Error &error)
+{
+ 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) {
+ error.Set(opus_encoder_domain, "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, Error &error)
+{
+ 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);
+}
+
+static bool
+opus_encoder_flush(Encoder *_encoder, gcc_unused Error &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,
+ Error &error)
+{
+ 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))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+opus_encoder_write(Encoder *_encoder,
+ const void *_data, size_t length,
+ Error &error)
+{
+ 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))
+ 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))
+ 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) = ToLE16(encoder->lookahead);
+ *(uint32_t *)(header + 12) =
+ ToLE32(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 *)xalloc(comments_size);
+ memcpy(comments, "OpusTags", 8);
+ *(uint32_t *)(comments + 8) = ToLE32(version_length);
+ memcpy(comments + 12, version, version_length);
+ *(uint32_t *)(comments + 12 + version_length) = ToLE32(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();
+
+ 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(gcc_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/plugins/OpusEncoderPlugin.hxx b/src/encoder/plugins/OpusEncoderPlugin.hxx
new file mode 100644
index 000000000..4e71694b9
--- /dev/null
+++ b/src/encoder/plugins/OpusEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_OPUS_H
+#define MPD_ENCODER_OPUS_H
+
+extern const struct EncoderPlugin opus_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/ShineEncoderPlugin.cxx b/src/encoder/plugins/ShineEncoderPlugin.cxx
new file mode 100644
index 000000000..61cb8609e
--- /dev/null
+++ b/src/encoder/plugins/ShineEncoderPlugin.cxx
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "ShineEncoderPlugin.hxx"
+#include "config.h"
+#include "../EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Manual.hxx"
+#include "util/NumberParser.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+extern "C"
+{
+#include <shine/layer3.h>
+}
+
+static constexpr size_t BUFFER_INIT_SIZE = 8192;
+static constexpr unsigned CHANNELS = 2;
+
+struct ShineEncoder {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+
+ shine_t shine;
+
+ shine_config_t config;
+
+ size_t frame_size;
+ size_t input_pos;
+ int16_t *stereo[CHANNELS];
+
+ Manual<DynamicFifoBuffer<uint8_t>> output_buffer;
+
+ ShineEncoder():encoder(shine_encoder_plugin){}
+
+ bool Configure(const config_param &param, Error &error);
+
+ bool Setup(Error &error);
+
+ bool WriteChunk(bool flush);
+};
+
+static constexpr Domain shine_encoder_domain("shine_encoder");
+
+inline bool
+ShineEncoder::Configure(const config_param &param,
+ gcc_unused Error &error)
+{
+ shine_set_config_mpeg_defaults(&config.mpeg);
+ config.mpeg.bitr = param.GetBlockValue("bitrate", 128);
+
+ return true;
+}
+
+static Encoder *
+shine_encoder_init(const config_param &param, Error &error)
+{
+ ShineEncoder *encoder = new ShineEncoder();
+
+ /* load configuration from "param" */
+ if (!encoder->Configure(param, error)) {
+ /* configuration has failed, roll back and return error */
+ delete encoder;
+ return nullptr;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+shine_encoder_finish(Encoder *_encoder)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ delete encoder;
+}
+
+inline bool
+ShineEncoder::Setup(Error &error)
+{
+ config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO;
+ config.wave.samplerate = audio_format.sample_rate;
+ config.wave.channels =
+ audio_format.channels == 2 ? PCM_STEREO : PCM_MONO;
+
+ if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
+ error.Format(config_domain,
+ "error configuring shine. "
+ "samplerate %d and bitrate %d configuration"
+ " not supported.",
+ config.wave.samplerate,
+ config.mpeg.bitr);
+
+ return false;
+ }
+
+ shine = shine_initialise(&config);
+
+ if (!shine) {
+ error.Format(config_domain,
+ "error initializing shine.");
+
+ return false;
+ }
+
+ frame_size = shine_samples_per_pass(shine);
+
+ return true;
+}
+
+static bool
+shine_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ audio_format.format = SampleFormat::S16;
+ audio_format.channels = CHANNELS;
+ encoder->audio_format = audio_format;
+
+ if (!encoder->Setup(error))
+ return false;
+
+ encoder->stereo[0] = new int16_t[encoder->frame_size];
+ encoder->stereo[1] = new int16_t[encoder->frame_size];
+ /* workaround for bug:
+ https://github.com/savonet/shine/issues/11 */
+ encoder->input_pos = SHINE_MAX_SAMPLES + 1;
+
+ encoder->output_buffer.Construct(BUFFER_INIT_SIZE);
+
+ return true;
+}
+
+static void
+shine_encoder_close(Encoder *_encoder)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ if (encoder->input_pos > SHINE_MAX_SAMPLES) {
+ /* write zero chunk */
+ encoder->input_pos = 0;
+ encoder->WriteChunk(true);
+ }
+
+ shine_close(encoder->shine);
+ delete[] encoder->stereo[0];
+ delete[] encoder->stereo[1];
+ encoder->output_buffer.Destruct();
+}
+
+bool
+ShineEncoder::WriteChunk(bool flush)
+{
+ if (flush || input_pos == frame_size) {
+ if (flush) {
+ /* fill remaining with 0s */
+ for (; input_pos < frame_size; input_pos++) {
+ stereo[0][input_pos] = stereo[1][input_pos] = 0;
+ }
+ }
+
+ int written;
+ const uint8_t *out =
+ shine_encode_buffer(shine, stereo, &written);
+
+ if (written > 0)
+ output_buffer->Append(out, written);
+
+ input_pos = 0;
+ }
+
+ return true;
+}
+
+static bool
+shine_encoder_write(Encoder *_encoder,
+ const void *_data, size_t length,
+ gcc_unused Error &error)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+ const int16_t *data = (const int16_t*)_data;
+ length /= sizeof(*data) * encoder->audio_format.channels;
+ size_t written = 0;
+
+ if (encoder->input_pos > SHINE_MAX_SAMPLES) {
+ encoder->input_pos = 0;
+ }
+
+ /* write all data to de-interleaved buffers */
+ while (written < length) {
+ for (;
+ written < length
+ && encoder->input_pos < encoder->frame_size;
+ written++, encoder->input_pos++) {
+ const size_t base =
+ written * encoder->audio_format.channels;
+ encoder->stereo[0][encoder->input_pos] = data[base];
+ encoder->stereo[1][encoder->input_pos] = data[base + 1];
+ }
+ /* write if chunk is filled */
+ encoder->WriteChunk(false);
+ }
+
+ return true;
+}
+
+static bool
+shine_encoder_flush(Encoder *_encoder, gcc_unused Error &error)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ /* flush buffers and flush shine */
+ encoder->WriteChunk(true);
+
+ int written;
+ const uint8_t *data = shine_flush(encoder->shine, &written);
+
+ if (written > 0)
+ encoder->output_buffer->Append(data, written);
+
+ return true;
+}
+
+static size_t
+shine_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ ShineEncoder *encoder = (ShineEncoder *)_encoder;
+
+ return encoder->output_buffer->Read((uint8_t *)dest, length);
+}
+
+static const char *
+shine_encoder_get_mime_type(gcc_unused Encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
+const EncoderPlugin shine_encoder_plugin = {
+ "shine",
+ shine_encoder_init,
+ shine_encoder_finish,
+ shine_encoder_open,
+ shine_encoder_close,
+ shine_encoder_flush,
+ shine_encoder_flush,
+ nullptr,
+ nullptr,
+ shine_encoder_write,
+ shine_encoder_read,
+ shine_encoder_get_mime_type,
+};
diff --git a/src/encoder/plugins/ShineEncoderPlugin.hxx b/src/encoder/plugins/ShineEncoderPlugin.hxx
new file mode 100644
index 000000000..8b1520a74
--- /dev/null
+++ b/src/encoder/plugins/ShineEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_SHINE_HXX
+#define MPD_ENCODER_SHINE_HXX
+
+extern const struct EncoderPlugin shine_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/TwolameEncoderPlugin.cxx b/src/encoder/plugins/TwolameEncoderPlugin.cxx
new file mode 100644
index 000000000..2eb6b2b1c
--- /dev/null
+++ b/src/encoder/plugins/TwolameEncoderPlugin.cxx
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TwolameEncoderPlugin.hxx"
+#include "../EncoderAPI.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigError.hxx"
+#include "util/NumberParser.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <twolame.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct TwolameEncoder final {
+ Encoder encoder;
+
+ AudioFormat audio_format;
+ float quality;
+ int bitrate;
+
+ twolame_options *options;
+
+ unsigned char output_buffer[32768];
+ size_t output_buffer_length;
+ size_t output_buffer_position;
+
+ /**
+ * Call libtwolame's flush function when the output_buffer is
+ * empty?
+ */
+ bool flush;
+
+ TwolameEncoder():encoder(twolame_encoder_plugin) {}
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain twolame_encoder_domain("twolame_encoder");
+
+bool
+TwolameEncoder::Configure(const config_param &param, Error &error)
+{
+ const char *value;
+ char *endptr;
+
+ value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ /* a quality was configured (VBR) */
+
+ quality = ParseDouble(value, &endptr);
+
+ if (*endptr != '\0' || quality < -1.0 || quality > 10.0) {
+ error.Format(config_domain,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10",
+ value);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ error.Set(config_domain,
+ "quality and bitrate are both defined");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ error.Set(config_domain,
+ "neither bitrate nor quality defined");
+ return false;
+ }
+
+ quality = -2.0;
+ bitrate = ParseInt(value, &endptr);
+
+ if (*endptr != '\0' || bitrate <= 0) {
+ error.Set(config_domain,
+ "bitrate should be a positive integer");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Encoder *
+twolame_encoder_init(const config_param &param, Error &error_r)
+{
+ FormatDebug(twolame_encoder_domain,
+ "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, Error &error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != twolame_set_VBR(encoder->options, true)) {
+ error.Set(twolame_encoder_domain,
+ "error setting twolame VBR mode");
+ return false;
+ }
+ if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
+ error.Set(twolame_encoder_domain,
+ "error setting twolame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
+ error.Set(twolame_encoder_domain,
+ "error setting twolame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != twolame_set_num_channels(encoder->options,
+ encoder->audio_format.channels)) {
+ error.Set(twolame_encoder_domain,
+ "error setting twolame num channels");
+ return false;
+ }
+
+ if (0 != twolame_set_in_samplerate(encoder->options,
+ encoder->audio_format.sample_rate)) {
+ error.Set(twolame_encoder_domain,
+ "error setting twolame sample rate");
+ return false;
+ }
+
+ if (0 > twolame_init_params(encoder->options)) {
+ error.Set(twolame_encoder_domain,
+ "error initializing twolame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+twolame_encoder_open(Encoder *_encoder, AudioFormat &audio_format,
+ Error &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) {
+ error.Set(twolame_encoder_domain, "twolame_init() failed");
+ return false;
+ }
+
+ if (!twolame_encoder_setup(encoder, error)) {
+ twolame_close(&encoder->options);
+ return false;
+ }
+
+ encoder->output_buffer_length = 0;
+ encoder->output_buffer_position = 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 Error &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 Error &error)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+ const int16_t *src = (const int16_t*)data;
+
+ assert(encoder->output_buffer_position ==
+ encoder->output_buffer_length);
+
+ const unsigned num_frames =
+ length / encoder->audio_format.GetFrameSize();
+
+ int bytes_out = twolame_encode_buffer_interleaved(encoder->options,
+ src, num_frames,
+ encoder->output_buffer,
+ sizeof(encoder->output_buffer));
+ if (bytes_out < 0) {
+ error.Set(twolame_encoder_domain, "twolame encoder failed");
+ return false;
+ }
+
+ encoder->output_buffer_length = (size_t)bytes_out;
+ encoder->output_buffer_position = 0;
+ return true;
+}
+
+static size_t
+twolame_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ TwolameEncoder *encoder = (TwolameEncoder *)_encoder;
+
+ assert(encoder->output_buffer_position <=
+ encoder->output_buffer_length);
+
+ if (encoder->output_buffer_position == encoder->output_buffer_length &&
+ encoder->flush) {
+ int ret = twolame_encode_flush(encoder->options,
+ encoder->output_buffer,
+ sizeof(encoder->output_buffer));
+ if (ret > 0) {
+ encoder->output_buffer_length = (size_t)ret;
+ encoder->output_buffer_position = 0;
+ }
+
+ encoder->flush = false;
+ }
+
+
+ const size_t remainning = encoder->output_buffer_length
+ - encoder->output_buffer_position;
+ if (length > remainning)
+ length = remainning;
+
+ memcpy(dest, encoder->output_buffer + encoder->output_buffer_position,
+ length);
+
+ encoder->output_buffer_position += 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/plugins/TwolameEncoderPlugin.hxx b/src/encoder/plugins/TwolameEncoderPlugin.hxx
new file mode 100644
index 000000000..531dd3e90
--- /dev/null
+++ b/src/encoder/plugins/TwolameEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_TWOLAME_HXX
+#define MPD_ENCODER_TWOLAME_HXX
+
+extern const struct EncoderPlugin twolame_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/VorbisEncoderPlugin.cxx b/src/encoder/plugins/VorbisEncoderPlugin.cxx
new file mode 100644
index 000000000..ecc784a47
--- /dev/null
+++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VorbisEncoderPlugin.hxx"
+#include "OggStream.hxx"
+#include "OggSerial.hxx"
+#include "../EncoderAPI.hxx"
+#include "tag/Tag.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigError.hxx"
+#include "util/NumberParser.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <vorbis/vorbisenc.h>
+
+#include <glib.h>
+
+struct vorbis_encoder {
+ /** the base class */
+ Encoder encoder;
+
+ /* 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 constexpr Domain vorbis_encoder_domain("vorbis_encoder");
+
+static bool
+vorbis_encoder_configure(struct vorbis_encoder *encoder,
+ const config_param &param, Error &error)
+{
+ const char *value = param.GetBlockValue("quality");
+ if (value != nullptr) {
+ /* a quality was configured (VBR) */
+
+ char *endptr;
+ encoder->quality = ParseDouble(value, &endptr);
+
+ if (*endptr != '\0' || encoder->quality < -1.0 ||
+ encoder->quality > 10.0) {
+ error.Format(config_domain,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10",
+ value);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ error.Set(config_domain,
+ "quality and bitrate are both defined");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ error.Set(config_domain,
+ "neither bitrate nor quality defined");
+ return false;
+ }
+
+ encoder->quality = -2.0;
+
+ char *endptr;
+ encoder->bitrate = ParseInt(value, &endptr);
+ if (*endptr != '\0' || encoder->bitrate <= 0) {
+ error.Set(config_domain,
+ "bitrate should be a positive integer");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Encoder *
+vorbis_encoder_init(const config_param &param, Error &error)
+{
+ vorbis_encoder *encoder = new vorbis_encoder();
+
+ /* load configuration from "param" */
+ if (!vorbis_encoder_configure(encoder, param, error)) {
+ /* 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, Error &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)) {
+ error.Set(vorbis_encoder_domain,
+ "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)) {
+ error.Set(vorbis_encoder_domain,
+ "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(GenerateOggSerial());
+
+ 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,
+ Error &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, gcc_unused Error &error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ encoder->stream.Flush();
+ return true;
+}
+
+static bool
+vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error)
+{
+ struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
+
+ 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 (const auto &item : *tag) {
+ 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,
+ gcc_unused Error &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(GenerateOggSerial());
+
+ /* 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,
+ gcc_unused Error &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(gcc_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/plugins/VorbisEncoderPlugin.hxx b/src/encoder/plugins/VorbisEncoderPlugin.hxx
new file mode 100644
index 000000000..80703bf88
--- /dev/null
+++ b/src/encoder/plugins/VorbisEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_VORBIS_H
+#define MPD_ENCODER_VORBIS_H
+
+extern const struct EncoderPlugin vorbis_encoder_plugin;
+
+#endif
diff --git a/src/encoder/plugins/WaveEncoderPlugin.cxx b/src/encoder/plugins/WaveEncoderPlugin.cxx
new file mode 100644
index 000000000..97a26e821
--- /dev/null
+++ b/src/encoder/plugins/WaveEncoderPlugin.cxx
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "WaveEncoderPlugin.hxx"
+#include "../EncoderAPI.hxx"
+#include "system/ByteOrder.hxx"
+#include "util/Manual.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+struct WaveEncoder {
+ Encoder encoder;
+ unsigned bits;
+
+ Manual<DynamicFifoBuffer<uint8_t>> 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 = ToLE32(0x46464952);
+ header->id_wave = ToLE32(0x45564157);
+ header->id_fmt = ToLE32(0x20746d66);
+ header->id_data = ToLE32(0x61746164);
+
+ /* wave format */
+ header->format = ToLE16(1); // PCM_FORMAT
+ header->channels = ToLE16(channels);
+ header->bits = ToLE16(bits);
+ header->freq = ToLE32(freq);
+ header->blocksize = ToLE16(block_size);
+ header->byterate = ToLE32(freq * block_size);
+
+ /* chunk sizes (fake data length) */
+ header->fmt_size = ToLE32(16);
+ header->data_size = ToLE32(data_size);
+ header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
+}
+
+static Encoder *
+wave_encoder_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ WaveEncoder *encoder = new WaveEncoder();
+ return &encoder->encoder;
+}
+
+static void
+wave_encoder_finish(Encoder *_encoder)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ delete encoder;
+}
+
+static bool
+wave_encoder_open(Encoder *_encoder,
+ AudioFormat &audio_format,
+ gcc_unused Error &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.Construct(8192);
+
+ auto range = encoder->buffer->Write();
+ assert(range.size >= sizeof(wave_header));
+ wave_header *header = (wave_header *)range.data;
+
+ /* create PCM wave header in initial buffer */
+ fill_wave_header(header,
+ audio_format.channels,
+ encoder->bits,
+ audio_format.sample_rate,
+ (encoder->bits / 8) * audio_format.channels);
+
+ encoder->buffer->Append(sizeof(*header));
+
+ return true;
+}
+
+static void
+wave_encoder_close(Encoder *_encoder)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ encoder->buffer.Destruct();
+}
+
+static size_t
+pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
+{
+ size_t cnt = length >> 1;
+ while (cnt > 0) {
+ *dst16++ = ToLE16(*src16++);
+ cnt--;
+ }
+ return length;
+}
+
+static size_t
+pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length)
+{
+ size_t cnt = length >> 2;
+ while (cnt > 0){
+ *dst32++ = ToLE32(*src32++);
+ cnt--;
+ }
+ return length;
+}
+
+static 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 Error &error)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ uint8_t *dst = encoder->buffer->Write(length);
+
+ if (IsLittleEndian()) {
+ 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;
+ }
+ } else {
+ switch (encoder->bits) {
+ case 8:
+ memcpy(dst, src, length);
+ break;
+ case 16:
+ length = pcm16_to_wave((uint16_t *)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((uint32_t *)dst,
+ (const uint32_t *)src, length);
+ break;
+ }
+ }
+
+ encoder->buffer->Append(length);
+ return true;
+}
+
+static size_t
+wave_encoder_read(Encoder *_encoder, void *dest, size_t length)
+{
+ WaveEncoder *encoder = (WaveEncoder *)_encoder;
+
+ return encoder->buffer->Read((uint8_t *)dest, 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/plugins/WaveEncoderPlugin.hxx b/src/encoder/plugins/WaveEncoderPlugin.hxx
new file mode 100644
index 000000000..341b98adc
--- /dev/null
+++ b/src/encoder/plugins/WaveEncoderPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ENCODER_WAVE_HXX
+#define MPD_ENCODER_WAVE_HXX
+
+extern const struct EncoderPlugin wave_encoder_plugin;
+
+#endif
diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx
index c93ea34c5..939824baa 100644
--- a/src/event/BufferedSocket.cxx
+++ b/src/event/BufferedSocket.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,9 @@
#include "system/SocketError.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "Compiler.h"
+
+#include <algorithm>
BufferedSocket::ssize_t
BufferedSocket::DirectRead(void *data, size_t length)
diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx
index db920f981..b1882de2f 100644
--- a/src/event/BufferedSocket.hxx
+++ b/src/event/BufferedSocket.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,20 +22,19 @@
#include "check.h"
#include "SocketMonitor.hxx"
-#include "util/FifoBuffer.hxx"
-#include "Compiler.h"
+#include "util/StaticFifoBuffer.hxx"
#include <assert.h>
#include <stdint.h>
-struct fifo_buffer;
class Error;
+class EventLoop;
/**
* A #SocketMonitor specialization that adds an input buffer.
*/
class BufferedSocket : protected SocketMonitor {
- FifoBuffer<uint8_t, 8192> input;
+ StaticFifoBuffer<uint8_t, 8192> input;
public:
BufferedSocket(int _fd, EventLoop &_loop)
diff --git a/src/event/Call.cxx b/src/event/Call.cxx
index ab1d5ffbd..bc16c4e95 100644
--- a/src/event/Call.cxx
+++ b/src/event/Call.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,9 +28,7 @@
#include <assert.h>
class BlockingCallMonitor final
-#ifndef USE_EPOLL
: DeferredMonitor
-#endif
{
const std::function<void()> f;
@@ -40,24 +38,13 @@ class BlockingCallMonitor final
bool done;
public:
-#ifdef USE_EPOLL
- BlockingCallMonitor(EventLoop &loop, std::function<void()> &&_f)
- :f(std::move(_f)), done(false) {
- loop.AddCall([this](){
- this->DoRun();
- });
- }
-#else
BlockingCallMonitor(EventLoop &_loop, std::function<void()> &&_f)
:DeferredMonitor(_loop), f(std::move(_f)), done(false) {}
-#endif
void Run() {
-#ifndef USE_EPOLL
assert(!done);
Schedule();
-#endif
mutex.lock();
while (!done)
@@ -65,16 +52,8 @@ public:
mutex.unlock();
}
-#ifndef USE_EPOLL
private:
virtual void RunDeferred() override {
- DoRun();
- }
-
-#else
-public:
-#endif
- void DoRun() {
assert(!done);
f();
diff --git a/src/event/Call.hxx b/src/event/Call.hxx
index 34d886ca5..808965de1 100644
--- a/src/event/Call.hxx
+++ b/src/event/Call.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/event/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx
index 62edb7817..3e824012f 100644
--- a/src/event/DeferredMonitor.cxx
+++ b/src/event/DeferredMonitor.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,66 +24,11 @@
void
DeferredMonitor::Cancel()
{
-#ifdef USE_EPOLL
- pending = false;
-#else
- const ScopeLock protect(mutex);
- if (source_id != 0) {
- g_source_remove(source_id);
- source_id = 0;
- }
-#endif
+ loop.RemoveDeferred(*this);
}
void
DeferredMonitor::Schedule()
{
-#ifdef USE_EPOLL
- if (!pending.exchange(true))
- fd.Write();
-#else
- const ScopeLock protect(mutex);
- if (source_id == 0)
- source_id = loop.AddIdle(Callback, this);
-#endif
+ loop.AddDeferred(*this);
}
-
-#ifdef USE_EPOLL
-
-bool
-DeferredMonitor::OnSocketReady(unsigned)
-{
- fd.Read();
-
- if (pending.exchange(false))
- RunDeferred();
-
- return true;
-}
-
-#else
-
-void
-DeferredMonitor::Run()
-{
- {
- const ScopeLock protect(mutex);
- if (source_id == 0)
- /* cancelled */
- return;
-
- source_id = 0;
- }
-
- RunDeferred();
-}
-
-gboolean
-DeferredMonitor::Callback(gpointer data)
-{
- DeferredMonitor &monitor = *(DeferredMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/DeferredMonitor.hxx b/src/event/DeferredMonitor.hxx
index 2ac832a0a..c4aa605fc 100644
--- a/src/event/DeferredMonitor.hxx
+++ b/src/event/DeferredMonitor.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,66 +21,30 @@
#define MPD_SOCKET_DEFERRED_MONITOR_HXX
#include "check.h"
-#include "Compiler.h"
-
-#ifdef USE_EPOLL
-#include "SocketMonitor.hxx"
-#include "WakeFD.hxx"
-#else
-#include "thread/Mutex.hxx"
-#include <glib.h>
-#endif
-
-#include <atomic>
class EventLoop;
/**
* Defer execution of an event into an #EventLoop.
+ *
+ * This class is thread-safe.
*/
-class DeferredMonitor
-#ifdef USE_EPOLL
- : private SocketMonitor
-#endif
-{
-#ifdef USE_EPOLL
- std::atomic_bool pending;
- WakeFD fd;
-#else
+class DeferredMonitor {
EventLoop &loop;
- Mutex mutex;
-
- guint source_id;
-#endif
+ friend class EventLoop;
+ bool pending;
public:
-#ifdef USE_EPOLL
DeferredMonitor(EventLoop &_loop)
- :SocketMonitor(_loop), pending(false) {
- SocketMonitor::Open(fd.Get());
- SocketMonitor::Schedule(SocketMonitor::READ);
- }
-#else
- DeferredMonitor(EventLoop &_loop)
- :loop(_loop), source_id(0) {}
-#endif
+ :loop(_loop), pending(false) {}
~DeferredMonitor() {
-#ifdef USE_EPOLL
- /* avoid closing the WakeFD twice */
- SocketMonitor::Steal();
-#else
Cancel();
-#endif
}
EventLoop &GetEventLoop() {
-#ifdef USE_EPOLL
- return SocketMonitor::GetEventLoop();
-#else
return loop;
-#endif
}
void Schedule();
@@ -88,14 +52,6 @@ public:
protected:
virtual void RunDeferred() = 0;
-
-private:
-#ifdef USE_EPOLL
- virtual bool OnSocketReady(unsigned flags) override final;
-#else
- void Run();
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx
index 8b57b1308..457add2b0 100644
--- a/src/event/FullyBufferedSocket.cxx
+++ b/src/event/FullyBufferedSocket.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,9 +20,9 @@
#include "config.h"
#include "FullyBufferedSocket.hxx"
#include "system/SocketError.hxx"
-#include "util/fifo_buffer.h"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "Compiler.h"
#include <assert.h>
#include <stdint.h>
@@ -59,15 +59,14 @@ FullyBufferedSocket::Flush()
{
assert(IsDefined());
- size_t length;
- const void *data = output.Read(&length);
- if (data == nullptr) {
+ const auto data = output.Read();
+ if (data.IsEmpty()) {
IdleMonitor::Cancel();
CancelWrite();
return true;
}
- auto nbytes = DirectWrite(data, length);
+ auto nbytes = DirectWrite(data.data, data.size);
if (gcc_unlikely(nbytes <= 0))
return nbytes == 0;
diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx
index c50bb5f61..b03152be2 100644
--- a/src/event/FullyBufferedSocket.hxx
+++ b/src/event/FullyBufferedSocket.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,7 +24,6 @@
#include "BufferedSocket.hxx"
#include "IdleMonitor.hxx"
#include "util/PeakBuffer.hxx"
-#include "Compiler.h"
/**
* A #BufferedSocket specialization that adds an output buffer.
diff --git a/src/event/IdleMonitor.cxx b/src/event/IdleMonitor.cxx
index c99c66b26..4af656a22 100644
--- a/src/event/IdleMonitor.cxx
+++ b/src/event/IdleMonitor.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,38 +21,31 @@
#include "IdleMonitor.hxx"
#include "Loop.hxx"
+#include <assert.h>
+
void
IdleMonitor::Cancel()
{
- assert(loop.IsInside());
+ assert(loop.IsInsideOrNull());
if (!IsActive())
return;
-#ifdef USE_EPOLL
active = false;
loop.RemoveIdle(*this);
-#else
- g_source_remove(source_id);
- source_id = 0;
-#endif
}
void
IdleMonitor::Schedule()
{
- assert(loop.IsInside());
+ assert(loop.IsInsideOrVirgin());
if (IsActive())
/* already scheduled */
return;
-#ifdef USE_EPOLL
active = true;
loop.AddIdle(*this);
-#else
- source_id = loop.AddIdle(Callback, this);
-#endif
}
void
@@ -60,25 +53,8 @@ IdleMonitor::Run()
{
assert(loop.IsInside());
-#ifdef USE_EPOLL
assert(active);
active = false;
-#else
- assert(source_id != 0);
- source_id = 0;
-#endif
OnIdle();
}
-
-#ifndef USE_EPOLL
-
-gboolean
-IdleMonitor::Callback(gpointer data)
-{
- IdleMonitor &monitor = *(IdleMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/IdleMonitor.hxx b/src/event/IdleMonitor.hxx
index c8e79eb1d..8d4d2681a 100644
--- a/src/event/IdleMonitor.hxx
+++ b/src/event/IdleMonitor.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,41 +22,34 @@
#include "check.h"
-#ifndef USE_EPOLL
-#include <glib.h>
-#endif
-
class EventLoop;
/**
* An event that runs when the EventLoop has become idle, before
- * waiting for more events. This class is not thread-safe; all
- * methods must be run from EventLoop's thread.
+ * waiting for more events.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
*/
class IdleMonitor {
-#ifdef USE_EPOLL
friend class EventLoop;
-#endif
EventLoop &loop;
-#ifdef USE_EPOLL
bool active;
-#else
- guint source_id;
-#endif
public:
-#ifdef USE_EPOLL
IdleMonitor(EventLoop &_loop)
:loop(_loop), active(false) {}
-#else
- IdleMonitor(EventLoop &_loop)
- :loop(_loop), source_id(0) {}
-#endif
~IdleMonitor() {
- Cancel();
+#ifndef NDEBUG
+ /* this check is redundant, it is only here to avoid
+ the assertion in Cancel() */
+ if (IsActive())
+#endif
+ Cancel();
}
EventLoop &GetEventLoop() const {
@@ -64,11 +57,7 @@ public:
}
bool IsActive() const {
-#ifdef USE_EPOLL
return active;
-#else
- return source_id != 0;
-#endif
}
void Schedule();
@@ -79,9 +68,6 @@ protected:
private:
void Run();
-#ifndef USE_EPOLL
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index 5aa24aea2..1bac7c551 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,20 +20,21 @@
#include "config.h"
#include "Loop.hxx"
-#ifdef USE_EPOLL
-
#include "system/Clock.hxx"
#include "TimeoutMonitor.hxx"
#include "SocketMonitor.hxx"
#include "IdleMonitor.hxx"
+#include "DeferredMonitor.hxx"
#include <algorithm>
-EventLoop::EventLoop(Default)
+EventLoop::EventLoop()
:SocketMonitor(*this),
now_ms(::MonotonicClockMS()),
- quit(false),
- n_events(0),
+ quit(false), busy(true),
+#ifndef NDEBUG
+ virgin(true),
+#endif
thread(ThreadId::Null())
{
SocketMonitor::Open(wake_fd.Get());
@@ -45,45 +46,51 @@ EventLoop::~EventLoop()
assert(idle.empty());
assert(timers.empty());
- /* avoid closing the WakeFD twice */
- SocketMonitor::Steal();
+ /* this is necessary to get a well-defined destruction
+ order */
+ SocketMonitor::Cancel();
}
void
EventLoop::Break()
{
- if (IsInside())
- quit = true;
- else
- AddCall([this]() { Break(); });
+ quit = true;
+ wake_fd.Write();
}
-void
-EventLoop::Abandon(SocketMonitor &m)
+bool
+EventLoop::Abandon(int _fd, SocketMonitor &m)
{
- for (unsigned i = 0, n = n_events; i < n; ++i)
- if (events[i].data.ptr == &m)
- events[i].events = 0;
+ assert(IsInsideOrVirgin());
+
+ poll_result.Clear(&m);
+ return poll_group.Abandon(_fd);
}
bool
EventLoop::RemoveFD(int _fd, SocketMonitor &m)
{
- Abandon(m);
- return epoll.Remove(_fd);
+ assert(IsInsideOrNull());
+
+ poll_result.Clear(&m);
+ return poll_group.Remove(_fd);
}
void
EventLoop::AddIdle(IdleMonitor &i)
{
+ assert(IsInsideOrVirgin());
assert(std::find(idle.begin(), idle.end(), &i) == idle.end());
idle.push_back(&i);
+ again = true;
}
void
EventLoop::RemoveIdle(IdleMonitor &i)
{
+ assert(IsInsideOrVirgin());
+
auto it = std::find(idle.begin(), idle.end(), &i);
assert(it != idle.end());
@@ -93,12 +100,19 @@ EventLoop::RemoveIdle(IdleMonitor &i)
void
EventLoop::AddTimer(TimeoutMonitor &t, unsigned ms)
{
+ /* can't use IsInsideOrVirgin() here because libavahi-client
+ modifies the timeout during avahi_client_free() */
+ assert(IsInsideOrNull());
+
timers.insert(TimerRecord(t, now_ms + ms));
+ again = true;
}
void
EventLoop::CancelTimer(TimeoutMonitor &t)
{
+ assert(IsInsideOrNull());
+
for (auto i = timers.begin(), end = timers.end(); i != end; ++i) {
if (&i->timer == &t) {
timers.erase(i);
@@ -107,19 +121,24 @@ EventLoop::CancelTimer(TimeoutMonitor &t)
}
}
-#endif
-
void
EventLoop::Run()
{
assert(thread.IsNull());
+ assert(virgin);
+
+#ifndef NDEBUG
+ virgin = false;
+#endif
+
thread = ThreadId::GetCurrent();
-#ifdef USE_EPOLL
assert(!quit);
+ assert(busy);
do {
now_ms = ::MonotonicClockMS();
+ again = false;
/* invoke timers */
@@ -146,7 +165,6 @@ EventLoop::Run()
/* invoke idle */
- const bool idle_empty = idle.empty();
while (!idle.empty()) {
IdleMonitor &m = *idle.front();
idle.pop_front();
@@ -156,7 +174,15 @@ EventLoop::Run()
return;
}
- if (!idle_empty)
+ /* try to handle DeferredMonitors without WakeFD
+ overhead */
+ mutex.lock();
+ HandleDeferred();
+ busy = false;
+ const bool _again = again;
+ mutex.unlock();
+
+ if (_again)
/* re-evaluate timers because one of the
IdleMonitors may have added a new
timeout */
@@ -164,101 +190,107 @@ EventLoop::Run()
/* wait for new event */
- const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms);
- n_events = std::max(n, 0);
+ poll_group.ReadEvents(poll_result, timeout_ms);
now_ms = ::MonotonicClockMS();
- assert(!quit);
+ mutex.lock();
+ busy = true;
+ mutex.unlock();
/* invoke sockets */
-
- for (int i = 0; i < n; ++i) {
- const auto &e = events[i];
-
- if (e.events != 0) {
- SocketMonitor &m = *(SocketMonitor *)e.data.ptr;
- m.Dispatch(e.events);
-
+ for (int i = 0; i < poll_result.GetSize(); ++i) {
+ auto events = poll_result.GetEvents(i);
+ if (events != 0) {
if (quit)
break;
+
+ auto m = (SocketMonitor *)poll_result.GetObject(i);
+ m->Dispatch(events);
}
}
- n_events = 0;
+ poll_result.Reset();
+
} while (!quit);
-#else
- g_main_loop_run(loop);
-#endif
+#ifndef NDEBUG
+ assert(busy);
assert(thread.IsInside());
+ thread = ThreadId::Null();
+#endif
}
-#ifdef USE_EPOLL
-
void
-EventLoop::AddCall(std::function<void()> &&f)
+EventLoop::AddDeferred(DeferredMonitor &d)
{
mutex.lock();
- calls.push_back(f);
+ if (d.pending) {
+ mutex.unlock();
+ return;
+ }
+
+ assert(std::find(deferred.begin(),
+ deferred.end(), &d) == deferred.end());
+
+ /* we don't need to wake up the EventLoop if another
+ DeferredMonitor has already done it */
+ const bool must_wake = !busy && deferred.empty();
+
+ d.pending = true;
+ deferred.push_back(&d);
+ again = true;
mutex.unlock();
- wake_fd.Write();
+ if (must_wake)
+ wake_fd.Write();
}
-bool
-EventLoop::OnSocketReady(gcc_unused unsigned flags)
+void
+EventLoop::RemoveDeferred(DeferredMonitor &d)
{
- assert(!quit);
+ const ScopeLock protect(mutex);
- wake_fd.Read();
+ if (!d.pending) {
+ assert(std::find(deferred.begin(),
+ deferred.end(), &d) == deferred.end());
+ return;
+ }
- mutex.lock();
+ d.pending = false;
- while (!calls.empty() && !quit) {
- auto f = std::move(calls.front());
- calls.pop_front();
+ auto i = std::find(deferred.begin(), deferred.end(), &d);
+ assert(i != deferred.end());
+
+ deferred.erase(i);
+}
+
+void
+EventLoop::HandleDeferred()
+{
+ while (!deferred.empty() && !quit) {
+ DeferredMonitor &m = *deferred.front();
+ assert(m.pending);
+
+ deferred.pop_front();
+ m.pending = false;
mutex.unlock();
- f();
+ m.RunDeferred();
mutex.lock();
}
-
- mutex.unlock();
-
- return true;
}
-#else
-
-guint
-EventLoop::AddIdle(GSourceFunc function, gpointer data)
+bool
+EventLoop::OnSocketReady(gcc_unused unsigned flags)
{
- GSource *source = g_idle_source_new();
- g_source_set_callback(source, function, data, nullptr);
- guint id = g_source_attach(source, GetContext());
- g_source_unref(source);
- return id;
-}
+ assert(IsInside());
-GSource *
-EventLoop::AddTimeout(guint interval_ms,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new(interval_ms);
- g_source_set_callback(source, function, data, nullptr);
- g_source_attach(source, GetContext());
- return source;
-}
+ wake_fd.Read();
-GSource *
-EventLoop::AddTimeoutSeconds(guint interval_s,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new_seconds(interval_s);
- g_source_set_callback(source, function, data, nullptr);
- g_source_attach(source, GetContext());
- return source;
-}
+ mutex.lock();
+ HandleDeferred();
+ mutex.unlock();
-#endif
+ return true;
+}
diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx
index 62e733747..56804dc81 100644
--- a/src/event/Loop.hxx
+++ b/src/event/Loop.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,33 +24,32 @@
#include "thread/Id.hxx"
#include "Compiler.h"
-#ifdef USE_EPOLL
-#include "system/EPollFD.hxx"
+#include "PollGroup.hxx"
#include "thread/Mutex.hxx"
#include "WakeFD.hxx"
#include "SocketMonitor.hxx"
-#include <functional>
#include <list>
#include <set>
-#else
-#include <glib.h>
-#endif
-#ifdef USE_EPOLL
class TimeoutMonitor;
class IdleMonitor;
+class DeferredMonitor;
class SocketMonitor;
-#endif
#include <assert.h>
-class EventLoop final
-#ifdef USE_EPOLL
- : private SocketMonitor
-#endif
+/**
+ * An event loop that polls for events on file/socket descriptors.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs it, except where explicitly documented as
+ * thread-safe.
+ *
+ * @see SocketMonitor, MultiSocketMonitor, TimeoutMonitor, IdleMonitor
+ */
+class EventLoop final : SocketMonitor
{
-#ifdef USE_EPOLL
struct TimerRecord {
/**
* Projected monotonic_clock_ms() value when this
@@ -73,52 +72,78 @@ class EventLoop final
}
};
- EPollFD epoll;
-
WakeFD wake_fd;
std::multiset<TimerRecord> timers;
std::list<IdleMonitor *> idle;
Mutex mutex;
- std::list<std::function<void()>> calls;
+ std::list<DeferredMonitor *> deferred;
unsigned now_ms;
bool quit;
- static constexpr unsigned MAX_EVENTS = 16;
- unsigned n_events;
- epoll_event events[MAX_EVENTS];
-#else
- GMainContext *context;
- GMainLoop *loop;
+ /**
+ * True when the object has been modified and another check is
+ * necessary before going to sleep via PollGroup::ReadEvents().
+ */
+ bool again;
+
+ /**
+ * True when handling callbacks, false when waiting for I/O or
+ * timeout.
+ *
+ * Protected with #mutex.
+ */
+ bool busy;
+
+#ifndef NDEBUG
+ /**
+ * True if Run() was never called. This is used for assert()
+ * calls.
+ */
+ bool virgin;
#endif
+ PollGroup poll_group;
+ PollResult poll_result;
+
/**
* A reference to the thread that is currently inside Run().
*/
ThreadId thread;
public:
-#ifdef USE_EPOLL
- struct Default {};
-
- EventLoop(Default dummy=Default());
+ EventLoop();
~EventLoop();
+ /**
+ * A caching wrapper for MonotonicClockMS().
+ */
unsigned GetTimeMS() const {
+ assert(IsInside());
+
return now_ms;
}
+ /**
+ * Stop execution of this #EventLoop at the next chance. This
+ * method is thread-safe and non-blocking: after returning, it
+ * is not guaranteed that the EventLoop has really stopped.
+ */
void Break();
bool AddFD(int _fd, unsigned flags, SocketMonitor &m) {
- return epoll.Add(_fd, flags, &m);
+ assert(thread.IsNull() || thread.IsInside());
+
+ return poll_group.Add(_fd, flags, &m);
}
bool ModifyFD(int _fd, unsigned flags, SocketMonitor &m) {
- return epoll.Modify(_fd, flags, &m);
+ assert(IsInside());
+
+ return poll_group.Modify(_fd, flags, &m);
}
/**
@@ -126,7 +151,7 @@ public:
* has been closed. This is like RemoveFD(), but does not
* attempt to use #EPOLL_CTL_DEL.
*/
- void Abandon(SocketMonitor &m);
+ bool Abandon(int fd, SocketMonitor &m);
bool RemoveFD(int fd, SocketMonitor &m);
@@ -136,53 +161,38 @@ public:
void AddTimer(TimeoutMonitor &t, unsigned ms);
void CancelTimer(TimeoutMonitor &t);
- void AddCall(std::function<void()> &&f);
+ /**
+ * Schedule a call to DeferredMonitor::RunDeferred().
+ *
+ * This method is thread-safe.
+ */
+ void AddDeferred(DeferredMonitor &d);
+ /**
+ * Cancel a pending call to DeferredMonitor::RunDeferred().
+ * However after returning, the call may still be running.
+ *
+ * This method is thread-safe.
+ */
+ void RemoveDeferred(DeferredMonitor &d);
+
+ /**
+ * The main function of this class. It will loop until
+ * Break() gets called. Can be called only once.
+ */
void Run();
private:
+ /**
+ * Invoke all pending DeferredMonitors.
+ *
+ * Caller must lock the mutex.
+ */
+ void HandleDeferred();
+
virtual bool OnSocketReady(unsigned flags) override;
public:
-#else
- EventLoop()
- :context(g_main_context_new()),
- loop(g_main_loop_new(context, false)),
- thread(ThreadId::Null()) {}
-
- struct Default {};
- EventLoop(gcc_unused Default _dummy)
- :context(g_main_context_ref(g_main_context_default())),
- loop(g_main_loop_new(context, false)),
- thread(ThreadId::Null()) {}
-
- ~EventLoop() {
- g_main_loop_unref(loop);
- g_main_context_unref(context);
- }
-
- GMainContext *GetContext() {
- return context;
- }
-
- void WakeUp() {
- g_main_context_wakeup(context);
- }
-
- void Break() {
- g_main_loop_quit(loop);
- }
-
- void Run();
-
- guint AddIdle(GSourceFunc function, gpointer data);
-
- GSource *AddTimeout(guint interval_ms,
- GSourceFunc function, gpointer data);
-
- GSource *AddTimeoutSeconds(guint interval_s,
- GSourceFunc function, gpointer data);
-#endif
/**
* Are we currently running inside this EventLoop's thread?
@@ -193,6 +203,20 @@ public:
return thread.IsInside();
}
+
+#ifndef NDEBUG
+ gcc_pure
+ bool IsInsideOrVirgin() const {
+ return virgin || IsInside();
+ }
+#endif
+
+#ifndef NDEBUG
+ gcc_pure
+ bool IsInsideOrNull() const {
+ return thread.IsNull() || thread.IsInside();
+ }
+#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx
index bd1aa6fef..ef77de425 100644
--- a/src/event/MultiSocketMonitor.cxx
+++ b/src/event/MultiSocketMonitor.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,12 +20,12 @@
#include "config.h"
#include "MultiSocketMonitor.hxx"
#include "Loop.hxx"
-#include "system/fd_util.h"
-#include "Compiler.h"
-#include <assert.h>
+#include <algorithm>
-#ifdef USE_EPOLL
+#ifndef WIN32
+#include <poll.h>
+#endif
MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
:IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) {
@@ -37,6 +37,40 @@ MultiSocketMonitor::~MultiSocketMonitor()
}
void
+MultiSocketMonitor::ClearSocketList()
+{
+ assert(GetEventLoop().IsInsideOrNull());
+
+ fds.clear();
+}
+
+#ifndef WIN32
+
+void
+MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n)
+{
+ pollfd *const end = pfds + n;
+
+ UpdateSocketList([pfds, end](int fd) -> unsigned {
+ auto i = std::find_if(pfds, end, [fd](const struct pollfd &pfd){
+ return pfd.fd == fd;
+ });
+ if (i == end)
+ return 0;
+
+ auto events = i->events;
+ i->events = 0;
+ return events;
+ });
+
+ for (auto i = pfds; i != end; ++i)
+ if (i->events != 0)
+ AddSocket(i->fd, i->events);
+}
+
+#endif
+
+void
MultiSocketMonitor::Prepare()
{
int timeout_ms = PrepareSockets();
@@ -64,100 +98,3 @@ MultiSocketMonitor::OnIdle()
Prepare();
}
}
-
-#else
-
-/**
- * The vtable for our GSource implementation. Unfortunately, we
- * cannot declare it "const", because g_source_new() takes a non-const
- * pointer, for whatever reason.
- */
-static GSourceFuncs multi_socket_monitor_source_funcs = {
- MultiSocketMonitor::Prepare,
- MultiSocketMonitor::Check,
- MultiSocketMonitor::Dispatch,
- nullptr,
- nullptr,
- nullptr,
-};
-
-MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop)
- :loop(_loop),
- source((Source *)g_source_new(&multi_socket_monitor_source_funcs,
- sizeof(*source))),
- absolute_timeout_us(-1) {
- source->monitor = this;
-
- g_source_attach(&source->base, loop.GetContext());
-}
-
-MultiSocketMonitor::~MultiSocketMonitor()
-{
- g_source_destroy(&source->base);
- g_source_unref(&source->base);
- source = nullptr;
-}
-
-bool
-MultiSocketMonitor::Prepare(gint *timeout_r)
-{
- int timeout_ms = *timeout_r = PrepareSockets();
- absolute_timeout_us = timeout_ms < 0
- ? uint64_t(-1)
- : GetTime() + uint64_t(timeout_ms) * 1000;
-
- return false;
-}
-
-bool
-MultiSocketMonitor::Check() const
-{
- if (GetTime() >= absolute_timeout_us)
- return true;
-
- for (const auto &i : fds)
- if (i.GetReturnedEvents() != 0)
- return true;
-
- return false;
-}
-
-/*
- * GSource methods
- *
- */
-
-gboolean
-MultiSocketMonitor::Prepare(GSource *_source, gint *timeout_r)
-{
- Source &source = *(Source *)_source;
- MultiSocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- return monitor.Prepare(timeout_r);
-}
-
-gboolean
-MultiSocketMonitor::Check(GSource *_source)
-{
- const Source &source = *(const Source *)_source;
- const MultiSocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- return monitor.Check();
-}
-
-gboolean
-MultiSocketMonitor::Dispatch(GSource *_source,
- gcc_unused GSourceFunc callback,
- gcc_unused gpointer user_data)
-{
- Source &source = *(Source *)_source;
- MultiSocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- monitor.Dispatch();
- return true;
-}
-
-#endif
diff --git a/src/event/MultiSocketMonitor.hxx b/src/event/MultiSocketMonitor.hxx
index 8ee81a508..b40ee8caa 100644
--- a/src/event/MultiSocketMonitor.hxx
+++ b/src/event/MultiSocketMonitor.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,40 +21,38 @@
#define MPD_MULTI_SOCKET_MONITOR_HXX
#include "check.h"
-#include "Compiler.h"
-
-#ifdef USE_EPOLL
#include "IdleMonitor.hxx"
#include "TimeoutMonitor.hxx"
#include "SocketMonitor.hxx"
-#else
-#include <glib.h>
-#endif
+#include "Compiler.h"
#include <forward_list>
+#include <iterator>
#include <assert.h>
-#include <stdint.h>
#ifdef WIN32
-/* ERRORis a WIN32 macro that poisons our namespace; this is a
- kludge to allow us to use it anyway */
+/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge
+ to allow us to use it anyway */
#ifdef ERROR
#undef ERROR
#endif
#endif
+#ifndef WIN32
+struct pollfd;
+#endif
+
class EventLoop;
/**
- * Monitor multiple sockets.
+ * Similar to #SocketMonitor, but monitors multiple sockets. To use
+ * it, implement the methods PrepareSockets() and DispatchSockets().
+ * In PrepareSockets(), use UpdateSocketList() and AddSocket().
+ * DispatchSockets() will be called if at least one socket is ready.
*/
-class MultiSocketMonitor
-#ifdef USE_EPOLL
- : private IdleMonitor, private TimeoutMonitor
-#endif
+class MultiSocketMonitor : IdleMonitor, TimeoutMonitor
{
-#ifdef USE_EPOLL
class SingleFD final : public SocketMonitor {
MultiSocketMonitor &multi;
@@ -99,93 +97,45 @@ class MultiSocketMonitor
friend class SingleFD;
bool ready, refresh;
-#else
- struct Source {
- GSource base;
-
- MultiSocketMonitor *monitor;
- };
-
- struct SingleFD {
- GPollFD pfd;
-
- constexpr SingleFD(gcc_unused MultiSocketMonitor &m,
- int fd, unsigned events)
- :pfd{fd, gushort(events), 0} {}
-
- constexpr int GetFD() const {
- return pfd.fd;
- }
-
- constexpr unsigned GetEvents() const {
- return pfd.events;
- }
-
- constexpr unsigned GetReturnedEvents() const {
- return pfd.revents;
- }
-
- void SetEvents(unsigned _events) {
- pfd.events = _events;
- }
- };
-
- EventLoop &loop;
- Source *source;
- uint64_t absolute_timeout_us;
-#endif
std::forward_list<SingleFD> fds;
public:
-#ifdef USE_EPOLL
static constexpr unsigned READ = SocketMonitor::READ;
static constexpr unsigned WRITE = SocketMonitor::WRITE;
static constexpr unsigned ERROR = SocketMonitor::ERROR;
static constexpr unsigned HANGUP = SocketMonitor::HANGUP;
-#else
- static constexpr unsigned READ = G_IO_IN;
- static constexpr unsigned WRITE = G_IO_OUT;
- static constexpr unsigned ERROR = G_IO_ERR;
- static constexpr unsigned HANGUP = G_IO_HUP;
-#endif
MultiSocketMonitor(EventLoop &_loop);
~MultiSocketMonitor();
-#ifdef USE_EPOLL
using IdleMonitor::GetEventLoop;
-#else
- EventLoop &GetEventLoop() {
- return loop;
- }
-#endif
public:
-#ifndef USE_EPOLL
- gcc_pure
- uint64_t GetTime() const {
- return g_source_get_time(&source->base);
- }
-#endif
-
+ /**
+ * Invalidate the socket list. A call to PrepareSockets() is
+ * scheduled which will then update the list.
+ */
void InvalidateSockets() {
-#ifdef USE_EPOLL
refresh = true;
IdleMonitor::Schedule();
-#else
- /* no-op because GLib always calls the GSource's
- "prepare" method before each poll() anyway */
-#endif
}
void AddSocket(int fd, unsigned events) {
fds.emplace_front(*this, fd, events);
-#ifndef USE_EPOLL
- g_source_add_poll(&source->base, &fds.front().pfd);
-#endif
}
+ /**
+ * Remove all sockets.
+ */
+ void ClearSocketList();
+
+ /**
+ * Update the known sockets by invoking the given function for
+ * each one; its return value is the events bit mask. A
+ * return value of 0 means the socket will be removed from the
+ * list.
+ */
template<typename E>
void UpdateSocketList(E &&e) {
for (auto prev = fds.before_begin(), end = fds.end(),
@@ -198,16 +148,19 @@ public:
i->SetEvents(events);
prev = i;
} else {
-#ifdef USE_EPOLL
- i->Steal();
-#else
- g_source_remove_poll(&source->base, &i->pfd);
-#endif
fds.erase_after(prev);
}
}
}
+#ifndef WIN32
+ /**
+ * Replace the socket list with the given file descriptors.
+ * The given pollfd array will be modified by this method.
+ */
+ void ReplaceSocketList(pollfd *pfds, unsigned n);
+#endif
+
protected:
/**
* @return timeout [ms] or -1 for no timeout
@@ -215,7 +168,6 @@ protected:
virtual int PrepareSockets() = 0;
virtual void DispatchSockets() = 0;
-#ifdef USE_EPOLL
private:
void SetReady() {
ready = true;
@@ -230,23 +182,6 @@ private:
}
virtual void OnIdle() final;
-
-#else
-public:
- /* GSource callbacks */
- static gboolean Prepare(GSource *source, gint *timeout_r);
- static gboolean Check(GSource *source);
- static gboolean Dispatch(GSource *source, GSourceFunc callback,
- gpointer user_data);
-
-private:
- bool Prepare(gint *timeout_r);
- bool Check() const;
-
- void Dispatch() {
- DispatchSockets();
- }
-#endif
};
#endif
diff --git a/src/event/PollGroup.hxx b/src/event/PollGroup.hxx
new file mode 100644
index 000000000..a2f176860
--- /dev/null
+++ b/src/event/PollGroup.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EVENT_POLLGROUP_HXX
+#define MPD_EVENT_POLLGROUP_HXX
+
+#ifdef USE_EPOLL
+#include "PollGroupEPoll.hxx"
+typedef PollResultEPoll PollResult;
+typedef PollGroupEPoll PollGroup;
+#endif
+
+#ifdef USE_WINSELECT
+#include "PollGroupWinSelect.hxx"
+typedef PollResultGeneric PollResult;
+typedef PollGroupWinSelect PollGroup;
+#endif
+
+#ifdef USE_POLL
+#include "PollGroupPoll.hxx"
+typedef PollResultGeneric PollResult;
+typedef PollGroupPoll PollGroup;
+#endif
+
+#endif
diff --git a/src/event/PollGroupEPoll.hxx b/src/event/PollGroupEPoll.hxx
new file mode 100644
index 000000000..d8edb8a1f
--- /dev/null
+++ b/src/event/PollGroupEPoll.hxx
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EVENT_POLLGROUP_EPOLL_HXX
+#define MPD_EVENT_POLLGROUP_EPOLL_HXX
+
+#include "check.h"
+
+#include "Compiler.h"
+#include "system/EPollFD.hxx"
+
+#include <array>
+#include <algorithm>
+
+class PollResultEPoll
+{
+ friend class PollGroupEPoll;
+
+ std::array<epoll_event, 16> events;
+ int n_events;
+public:
+ PollResultEPoll() : n_events(0) { }
+
+ int GetSize() const { return n_events; }
+ unsigned GetEvents(int i) const { return events[i].events; }
+ void *GetObject(int i) const { return events[i].data.ptr; }
+ void Reset() { n_events = 0; }
+
+ void Clear(void *obj) {
+ for (int i = 0; i < n_events; ++i)
+ if (events[i].data.ptr == obj)
+ events[i].events = 0;
+ }
+};
+
+class PollGroupEPoll
+{
+ EPollFD epoll;
+
+ PollGroupEPoll(PollGroupEPoll &) = delete;
+ PollGroupEPoll &operator=(PollGroupEPoll &) = delete;
+public:
+ static constexpr unsigned READ = EPOLLIN;
+ static constexpr unsigned WRITE = EPOLLOUT;
+ static constexpr unsigned ERROR = EPOLLERR;
+ static constexpr unsigned HANGUP = EPOLLHUP;
+
+ PollGroupEPoll() = default;
+
+ void ReadEvents(PollResultEPoll &result, int timeout_ms) {
+ int ret = epoll.Wait(result.events.data(), result.events.size(),
+ timeout_ms);
+ result.n_events = std::max(0, ret);
+ }
+
+ bool Add(int fd, unsigned events, void *obj) {
+ return epoll.Add(fd, events, obj);
+ }
+
+ bool Modify(int fd, unsigned events, void *obj) {
+ return epoll.Modify(fd, events, obj);
+ }
+
+ bool Remove(int fd) {
+ return epoll.Remove(fd);
+ }
+
+ bool Abandon(gcc_unused int fd) {
+ // Nothing to do in this implementation.
+ // Closed descriptors are automatically unregistered.
+ return true;
+ }
+};
+
+#endif
diff --git a/src/event/PollGroupPoll.cxx b/src/event/PollGroupPoll.cxx
new file mode 100644
index 000000000..402f8616f
--- /dev/null
+++ b/src/event/PollGroupPoll.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#ifdef USE_POLL
+
+#include "PollGroupPoll.hxx"
+
+#include <assert.h>
+
+PollGroupPoll::PollGroupPoll() { }
+PollGroupPoll::~PollGroupPoll() { }
+
+bool PollGroupPoll::Add(int fd, unsigned events, void *obj)
+{
+ assert(items.find(fd) == items.end());
+
+ const size_t index = poll_events.size();
+ poll_events.resize(index + 1);
+ auto &e = poll_events[index];
+ e.fd = fd;
+ e.events = events;
+ e.revents = 0;
+ auto &item = items[fd];
+ item.index = index;
+ item.obj = obj;
+ return true;
+}
+
+bool PollGroupPoll::Modify(int fd, unsigned events, void *obj)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+ item.obj = obj;
+ auto &e = poll_events[item.index];
+ e.events = events;
+ e.revents &= events;
+ return true;
+}
+
+bool PollGroupPoll::Remove(int fd)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+ size_t index = item.index;
+ size_t last_index = poll_events.size() - 1;
+ if (index != last_index) {
+ std::swap(poll_events[index], poll_events[last_index]);
+ items[poll_events[index].fd].index = index;
+ }
+ poll_events.pop_back();
+ items.erase(item_iter);
+ return true;
+}
+
+void PollGroupPoll::ReadEvents(PollResultGeneric &result, int timeout_ms)
+{
+ int n = poll(poll_events.empty() ? nullptr : &poll_events[0],
+ poll_events.size(), timeout_ms);
+
+ for (size_t i = 0; n > 0 && i < poll_events.size(); ++i) {
+ const auto &e = poll_events[i];
+ if (e.revents != 0) {
+ result.Add(e.revents, items[e.fd].obj);
+ --n;
+ }
+ }
+}
+
+#endif
diff --git a/src/event/PollGroupPoll.hxx b/src/event/PollGroupPoll.hxx
new file mode 100644
index 000000000..f7a3ccb4f
--- /dev/null
+++ b/src/event/PollGroupPoll.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EVENT_POLLGROUP_POLL_HXX
+#define MPD_EVENT_POLLGROUP_POLL_HXX
+
+#include "check.h"
+#include "PollResultGeneric.hxx"
+
+#include <vector>
+#include <unordered_map>
+
+#include <stddef.h>
+#include <sys/poll.h>
+
+class PollGroupPoll
+{
+ struct Item
+ {
+ size_t index;
+ void *obj;
+ };
+
+ std::vector<pollfd> poll_events;
+ std::unordered_map<int, Item> items;
+
+ PollGroupPoll(PollGroupPoll &) = delete;
+ PollGroupPoll &operator=(PollGroupPoll &) = delete;
+public:
+ static constexpr unsigned READ = POLLIN;
+ static constexpr unsigned WRITE = POLLOUT;
+ static constexpr unsigned ERROR = POLLERR;
+ static constexpr unsigned HANGUP = POLLHUP;
+
+ PollGroupPoll();
+ ~PollGroupPoll();
+
+ void ReadEvents(PollResultGeneric &result, int timeout_ms);
+ bool Add(int fd, unsigned events, void *obj);
+ bool Modify(int fd, unsigned events, void *obj);
+ bool Remove(int fd);
+ bool Abandon(int fd) {
+ return Remove(fd);
+ }
+};
+
+#endif
diff --git a/src/event/PollGroupWinSelect.cxx b/src/event/PollGroupWinSelect.cxx
new file mode 100644
index 000000000..26c8abd46
--- /dev/null
+++ b/src/event/PollGroupWinSelect.cxx
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#ifdef USE_WINSELECT
+
+#include "PollGroupWinSelect.hxx"
+
+constexpr int EVENT_READ = 0;
+constexpr int EVENT_WRITE = 1;
+
+static inline bool HasEvent(unsigned events, int event_id)
+{
+ return (events & (1 << event_id)) != 0;
+}
+
+PollGroupWinSelect::PollGroupWinSelect() { }
+PollGroupWinSelect::~PollGroupWinSelect() { }
+
+bool PollGroupWinSelect::CanModify(PollGroupWinSelect::Item &item,
+ unsigned events, int event_id)
+{
+ if (item.index[event_id] < 0 && HasEvent(events, event_id))
+ return !event_set[event_id].IsFull();
+ return true;
+}
+
+void PollGroupWinSelect::Modify(PollGroupWinSelect::Item &item, int fd,
+ unsigned events, int event_id)
+{
+ int index = item.index[event_id];
+ auto &set = event_set[event_id];
+
+ if (index < 0 && HasEvent(events, event_id))
+ item.index[event_id] = set.Add(fd);
+ else if (index >= 0 && !HasEvent(events, event_id)) {
+ if (index != set.Size() - 1) {
+ set.MoveToEnd(index);
+ items[set[index]].index[event_id] = index;
+ }
+ set.RemoveLast();
+ item.index[event_id] = -1;
+ }
+}
+
+bool PollGroupWinSelect::Add(int fd, unsigned events, void *obj)
+{
+ assert(items.find(fd) == items.end());
+ auto &item = items[fd];
+
+ item.index[EVENT_READ] = -1;
+ item.index[EVENT_WRITE] = -1;
+ item.obj = obj;
+ item.events = 0;
+
+ if (!CanModify(item, events, EVENT_READ)) {
+ items.erase(fd);
+ return false;
+ }
+ if (!CanModify(item, events, EVENT_WRITE)) {
+ items.erase(fd);
+ return false;
+ }
+
+ Modify(item, fd, events, EVENT_READ);
+ Modify(item, fd, events, EVENT_WRITE);
+ return true;
+}
+
+bool PollGroupWinSelect::Modify(int fd, unsigned events, void *obj)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+
+ if (!CanModify(item, events, EVENT_READ))
+ return false;
+ if (!CanModify(item, events, EVENT_WRITE))
+ return false;
+
+ item.obj = obj;
+ Modify(item, fd, events, EVENT_READ);
+ Modify(item, fd, events, EVENT_WRITE);
+ return true;
+}
+
+bool PollGroupWinSelect::Remove(int fd)
+{
+ auto item_iter = items.find(fd);
+ assert(item_iter != items.end());
+ auto &item = item_iter->second;
+
+ Modify(item, fd, 0, EVENT_READ);
+ Modify(item, fd, 0, EVENT_WRITE);
+ items.erase(item_iter);
+ return true;
+}
+
+void PollGroupWinSelect::ReadEvents(PollResultGeneric &result, int timeout_ms)
+{
+ bool use_sleep = event_set[EVENT_READ].IsEmpty() &&
+ event_set[EVENT_WRITE].IsEmpty();
+
+ if (use_sleep) {
+ Sleep(timeout_ms < 0 ? INFINITE : (DWORD) timeout_ms);
+ return;
+ }
+
+ SocketSet read_set(event_set[EVENT_READ]);
+ SocketSet write_set(event_set[EVENT_WRITE]);
+ SocketSet except_set(event_set[EVENT_WRITE]);
+
+ timeval tv;
+ if (timeout_ms >= 0) {
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+ }
+
+ int ret = select(0,
+ read_set.IsEmpty() ? nullptr : read_set.GetPtr(),
+ write_set.IsEmpty() ? nullptr : write_set.GetPtr(),
+ except_set.IsEmpty() ? nullptr : except_set.GetPtr(),
+ timeout_ms < 0 ? nullptr : &tv);
+
+ if (ret == 0 || ret == SOCKET_ERROR)
+ return;
+
+ for (int i = 0; i < read_set.Size(); ++i)
+ items[read_set[i]].events |= READ;
+
+ for (int i = 0; i < write_set.Size(); ++i)
+ items[write_set[i]].events |= WRITE;
+
+ for (int i = 0; i < except_set.Size(); ++i)
+ items[except_set[i]].events |= WRITE;
+
+ for (auto i = items.begin(); i != items.end(); ++i)
+ if (i->second.events != 0) {
+ result.Add(i->second.events, i->second.obj);
+ i->second.events = 0;
+ }
+}
+
+#endif
diff --git a/src/event/PollGroupWinSelect.hxx b/src/event/PollGroupWinSelect.hxx
new file mode 100644
index 000000000..d01067709
--- /dev/null
+++ b/src/event/PollGroupWinSelect.hxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EVENT_POLLGROUP_WINSELECT_HXX
+#define MPD_EVENT_POLLGROUP_WINSELECT_HXX
+
+#include "check.h"
+
+#include "PollResultGeneric.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#include <unordered_map>
+
+#include <windows.h>
+#include <winsock2.h>
+
+#ifdef ERROR
+#undef ERROR
+#endif
+
+class SocketSet
+{
+ fd_set set;
+public:
+ SocketSet() { set.fd_count = 0; }
+ SocketSet(SocketSet &other) {
+ set.fd_count = other.set.fd_count;
+ memcpy(set.fd_array,
+ other.set.fd_array,
+ sizeof (SOCKET) * set.fd_count);
+ }
+
+ fd_set *GetPtr() { return &set; }
+ int Size() { return set.fd_count; }
+ bool IsEmpty() { return set.fd_count == 0; }
+ bool IsFull() { return set.fd_count == FD_SETSIZE; }
+
+ int operator[](int index) {
+ assert(index >= 0 && (u_int)index < set.fd_count);
+ return set.fd_array[index];
+ }
+
+ int Add(int fd) {
+ assert(!IsFull());
+ set.fd_array[set.fd_count] = fd;
+ return set.fd_count++;
+ }
+
+ void MoveToEnd(int index) {
+ assert(index >= 0 && (u_int)index < set.fd_count);
+ std::swap(set.fd_array[index], set.fd_array[set.fd_count - 1]);
+ }
+
+ void RemoveLast() {
+ assert(!IsEmpty());
+ --set.fd_count;
+ }
+};
+
+class PollGroupWinSelect
+{
+ struct Item
+ {
+ int index[2];
+ void *obj;
+ unsigned events;
+ };
+
+ SocketSet event_set[2];
+ std::unordered_map<int, Item> items;
+
+ bool CanModify(Item &item, unsigned events, int event_id);
+ void Modify(Item &item, int fd, unsigned events, int event_id);
+
+ PollGroupWinSelect(PollGroupWinSelect &) = delete;
+ PollGroupWinSelect &operator=(PollGroupWinSelect &) = delete;
+public:
+ static constexpr unsigned READ = 1;
+ static constexpr unsigned WRITE = 2;
+ static constexpr unsigned ERROR = 0;
+ static constexpr unsigned HANGUP = 0;
+
+ PollGroupWinSelect();
+ ~PollGroupWinSelect();
+
+ void ReadEvents(PollResultGeneric &result, int timeout_ms);
+ bool Add(int fd, unsigned events, void *obj);
+ bool Modify(int fd, unsigned events, void *obj);
+ bool Remove(int fd);
+ bool Abandon(int fd) { return Remove(fd); }
+};
+
+#endif
diff --git a/src/event/PollResultGeneric.hxx b/src/event/PollResultGeneric.hxx
new file mode 100644
index 000000000..35daf7f08
--- /dev/null
+++ b/src/event/PollResultGeneric.hxx
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EVENT_POLLRESULT_GENERIC_HXX
+#define MPD_EVENT_POLLRESULT_GENERIC_HXX
+
+#include "check.h"
+
+#include <vector>
+
+class PollResultGeneric
+{
+ struct Item
+ {
+ unsigned events;
+ void *obj;
+
+ Item() = default;
+ Item(unsigned _events, void *_obj)
+ : events(_events), obj(_obj) { }
+ };
+
+ std::vector<Item> items;
+public:
+ int GetSize() const { return items.size(); }
+ unsigned GetEvents(int i) const { return items[i].events; }
+ void *GetObject(int i) const { return items[i].obj; }
+ void Reset() { items.clear(); }
+
+ void Clear(void *obj) {
+ for (auto i = items.begin(); i != items.end(); ++i)
+ if (i->obj == obj)
+ i->events = 0;
+ }
+
+ void Add(unsigned events, void *obj) {
+ items.emplace_back(events, obj);
+ }
+};
+
+#endif
diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx
index 361aba886..313f0a6cf 100644
--- a/src/event/ServerSocket.cxx
+++ b/src/event/ServerSocket.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,11 +18,6 @@
*/
#include "config.h"
-
-#ifdef HAVE_STRUCT_UCRED
-#define _GNU_SOURCE 1
-#endif
-
#include "ServerSocket.hxx"
#include "system/SocketUtil.hxx"
#include "system/SocketError.hxx"
@@ -31,13 +26,13 @@
#include "system/fd_util.h"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
+#include "util/Alloc.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <string>
+#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
@@ -78,7 +73,7 @@ public:
parent(_parent), serial(_serial),
path(AllocatedPath::Null()),
address_length(_address_length),
- address((sockaddr *)g_memdup(_address, _address_length))
+ address((sockaddr *)xmemdup(_address, _address_length))
{
assert(_address != nullptr);
assert(_address_length > 0);
@@ -88,7 +83,10 @@ public:
OneServerSocket &operator=(const OneServerSocket &other) = delete;
~OneServerSocket() {
- g_free(address);
+ free(address);
+
+ if (IsDefined())
+ Close();
}
unsigned GetSerial() const {
@@ -106,7 +104,10 @@ public:
using SocketMonitor::IsDefined;
using SocketMonitor::Close;
- char *ToString() const;
+ gcc_pure
+ std::string ToString() const {
+ return sockaddr_to_string(address, address_length);
+ }
void SetFD(int _fd) {
SocketMonitor::Open(_fd);
@@ -121,18 +122,6 @@ private:
static constexpr Domain server_socket_domain("server_socket");
-/**
- * Wraper for sockaddr_to_string() which never fails.
- */
-char *
-OneServerSocket::ToString() const
-{
- char *p = sockaddr_to_string(address, address_length, IgnoreError());
- if (p == nullptr)
- p = g_strdup("[unknown]");
- return p;
-}
-
static int
get_remote_uid(int fd)
{
@@ -242,23 +231,21 @@ ServerSocket::Open(Error &error)
Error error2;
if (!i.Open(error2)) {
if (good != nullptr && good->GetSerial() == i.GetSerial()) {
- char *address_string = i.ToString();
- char *good_string = good->ToString();
+ const auto address_string = i.ToString();
+ const auto good_string = good->ToString();
FormatWarning(server_socket_domain,
"bind to '%s' failed: %s "
"(continuing anyway, because "
"binding to '%s' succeeded)",
- address_string, error2.GetMessage(),
- good_string);
- g_free(address_string);
- g_free(good_string);
+ address_string.c_str(),
+ error2.GetMessage(),
+ good_string.c_str());
} else if (bad == nullptr) {
bad = &i;
- char *address_string = i.ToString();
+ const auto address_string = i.ToString();
error2.FormatPrefix("Failed to bind to '%s': ",
- address_string);
- g_free(address_string);
+ address_string.c_str());
last_error = std::move(error2);
}
diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx
index facb10371..4c3fd9f1d 100644
--- a/src/event/ServerSocket.hxx
+++ b/src/event/ServerSocket.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -36,6 +36,9 @@ typedef void (*server_socket_callback_t)(int fd,
class OneServerSocket;
+/**
+ * A socket that accepts incoming stream connections (e.g. TCP).
+ */
class ServerSocket {
friend class OneServerSocket;
diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx
index 4f5174377..2d8fe681f 100644
--- a/src/event/SignalMonitor.cxx
+++ b/src/event/SignalMonitor.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -43,6 +43,7 @@
#include <pthread.h>
#endif
+#include <assert.h>
#include <signal.h>
class SignalMonitor final : private SocketMonitor {
@@ -61,14 +62,6 @@ public:
#endif
}
- ~SignalMonitor() {
- /* prevent the descriptor to be closed twice */
-#ifdef USE_SIGNALFD
- if (SocketMonitor::IsDefined())
-#endif
- SocketMonitor::Steal();
- }
-
using SocketMonitor::GetEventLoop;
#ifdef USE_SIGNALFD
diff --git a/src/event/SignalMonitor.hxx b/src/event/SignalMonitor.hxx
index 1ecccd40b..a41e57ef9 100644
--- a/src/event/SignalMonitor.hxx
+++ b/src/event/SignalMonitor.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx
index 2b97059f7..69207287d 100644
--- a/src/event/SocketMonitor.cxx
+++ b/src/event/SocketMonitor.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,12 +28,9 @@
#ifdef WIN32
#include <winsock2.h>
#else
-#include <sys/types.h>
#include <sys/socket.h>
#endif
-#ifdef USE_EPOLL
-
void
SocketMonitor::Dispatch(unsigned flags)
{
@@ -43,93 +40,19 @@ SocketMonitor::Dispatch(unsigned flags)
Cancel();
}
-#else
-
-/*
- * GSource methods
- *
- */
-
-gboolean
-SocketMonitor::Prepare(gcc_unused GSource *source, gcc_unused gint *timeout_r)
-{
- return false;
-}
-
-gboolean
-SocketMonitor::Check(GSource *_source)
-{
- const Source &source = *(const Source *)_source;
- const SocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- return monitor.Check();
-}
-
-gboolean
-SocketMonitor::Dispatch(GSource *_source,
- gcc_unused GSourceFunc callback,
- gcc_unused gpointer user_data)
-{
- Source &source = *(Source *)_source;
- SocketMonitor &monitor = *source.monitor;
- assert(_source == &monitor.source->base);
-
- monitor.Dispatch();
- return true;
-}
-
-/**
- * The vtable for our GSource implementation. Unfortunately, we
- * cannot declare it "const", because g_source_new() takes a non-const
- * pointer, for whatever reason.
- */
-static GSourceFuncs socket_monitor_source_funcs = {
- SocketMonitor::Prepare,
- SocketMonitor::Check,
- SocketMonitor::Dispatch,
- nullptr,
- nullptr,
- nullptr,
-};
-
-SocketMonitor::SocketMonitor(int _fd, EventLoop &_loop)
- :fd(-1), loop(_loop),
- source(nullptr) {
- assert(_fd >= 0);
-
- Open(_fd);
-}
-
-#endif
-
SocketMonitor::~SocketMonitor()
{
if (IsDefined())
- Close();
+ Cancel();
}
void
SocketMonitor::Open(int _fd)
{
assert(fd < 0);
-#ifndef USE_EPOLL
- assert(source == nullptr);
-#endif
assert(_fd >= 0);
fd = _fd;
-
-#ifndef USE_EPOLL
- poll = {fd, 0, 0};
-
- source = (Source *)g_source_new(&socket_monitor_source_funcs,
- sizeof(*source));
- source->monitor = this;
-
- g_source_attach(&source->base, loop.GetContext());
- g_source_add_poll(&source->base, &poll);
-#endif
}
int
@@ -142,12 +65,6 @@ SocketMonitor::Steal()
int result = fd;
fd = -1;
-#ifndef USE_EPOLL
- g_source_destroy(&source->base);
- g_source_unref(&source->base);
- source = nullptr;
-#endif
-
return result;
}
@@ -156,12 +73,9 @@ SocketMonitor::Abandon()
{
assert(IsDefined());
-#ifdef USE_EPOLL
+ int old_fd = fd;
fd = -1;
- loop.Abandon(*this);
-#else
- Steal();
-#endif
+ loop.Abandon(old_fd, *this);
}
void
@@ -178,7 +92,6 @@ SocketMonitor::Schedule(unsigned flags)
if (flags == GetScheduledFlags())
return;
-#ifdef USE_EPOLL
if (scheduled_flags == 0)
loop.AddFD(fd, flags, *this);
else if (flags == 0)
@@ -187,12 +100,6 @@ SocketMonitor::Schedule(unsigned flags)
loop.ModifyFD(fd, flags, *this);
scheduled_flags = flags;
-#else
- poll.events = flags;
- poll.revents &= flags;
-
- loop.WakeUp();
-#endif
}
SocketMonitor::ssize_t
diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx
index 5369ddb8a..56d4273f0 100644
--- a/src/event/SocketMonitor.hxx
+++ b/src/event/SocketMonitor.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,12 +21,7 @@
#define MPD_SOCKET_MONITOR_HXX
#include "check.h"
-
-#ifdef USE_EPOLL
-#include <sys/epoll.h>
-#else
-#include <glib.h>
-#endif
+#include "PollGroup.hxx"
#include <type_traits>
@@ -34,8 +29,8 @@
#include <stddef.h>
#ifdef WIN32
-/* ERRORis a WIN32 macro that poisons our namespace; this is a
- kludge to allow us to use it anyway */
+/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge
+ to allow us to use it anyway */
#ifdef ERROR
#undef ERROR
#endif
@@ -43,56 +38,41 @@
class EventLoop;
+/**
+ * Monitor events on a socket. Call Schedule() to announce events
+ * you're interested in, or Cancel() to cancel your subscription. The
+ * #EventLoop will invoke virtual method OnSocketReady() as soon as
+ * any of the subscribed events are ready.
+ *
+ * This class does not feel responsible for closing the socket. Call
+ * Close() to do it manually.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
+ */
class SocketMonitor {
-#ifdef USE_EPOLL
-#else
- struct Source {
- GSource base;
-
- SocketMonitor *monitor;
- };
-#endif
-
int fd;
EventLoop &loop;
-#ifdef USE_EPOLL
/**
* A bit mask of events that is currently registered in the EventLoop.
*/
unsigned scheduled_flags;
-#else
- Source *source;
- GPollFD poll;
-#endif
public:
-#ifdef USE_EPOLL
- static constexpr unsigned READ = EPOLLIN;
- static constexpr unsigned WRITE = EPOLLOUT;
- static constexpr unsigned ERROR = EPOLLERR;
- static constexpr unsigned HANGUP = EPOLLHUP;
-#else
- static constexpr unsigned READ = G_IO_IN;
- static constexpr unsigned WRITE = G_IO_OUT;
- static constexpr unsigned ERROR = G_IO_ERR;
- static constexpr unsigned HANGUP = G_IO_HUP;
-#endif
+ static constexpr unsigned READ = PollGroup::READ;
+ static constexpr unsigned WRITE = PollGroup::WRITE;
+ static constexpr unsigned ERROR = PollGroup::ERROR;
+ static constexpr unsigned HANGUP = PollGroup::HANGUP;
typedef std::make_signed<size_t>::type ssize_t;
-#ifdef USE_EPOLL
SocketMonitor(EventLoop &_loop)
:fd(-1), loop(_loop), scheduled_flags(0) {}
SocketMonitor(int _fd, EventLoop &_loop)
:fd(_fd), loop(_loop), scheduled_flags(0) {}
-#else
- SocketMonitor(EventLoop &_loop)
- :fd(-1), loop(_loop), source(nullptr) {}
-
- SocketMonitor(int _fd, EventLoop &_loop);
-#endif
~SocketMonitor();
@@ -114,7 +94,7 @@ public:
/**
* "Steal" the socket descriptor. This abandons the socket
- * and puts the responsibility for closing it to the caller.
+ * and returns it.
*/
int Steal();
@@ -128,11 +108,7 @@ public:
unsigned GetScheduledFlags() const {
assert(IsDefined());
-#ifdef USE_EPOLL
return scheduled_flags;
-#else
- return poll.events;
-#endif
}
void Schedule(unsigned flags);
@@ -167,28 +143,7 @@ protected:
virtual bool OnSocketReady(unsigned flags) = 0;
public:
-#ifdef USE_EPOLL
void Dispatch(unsigned flags);
-#else
- /* GSource callbacks */
- static gboolean Prepare(GSource *source, gint *timeout_r);
- static gboolean Check(GSource *source);
- static gboolean Dispatch(GSource *source, GSourceFunc callback,
- gpointer user_data);
-
-private:
- bool Check() const {
- assert(IsDefined());
-
- return (poll.revents & poll.events) != 0;
- }
-
- void Dispatch() {
- assert(IsDefined());
-
- OnSocketReady(poll.revents & poll.events);
- }
-#endif
};
#endif
diff --git a/src/event/TimeoutMonitor.cxx b/src/event/TimeoutMonitor.cxx
index 003b5b78f..007e8aa2c 100644
--- a/src/event/TimeoutMonitor.cxx
+++ b/src/event/TimeoutMonitor.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,28 +25,19 @@ void
TimeoutMonitor::Cancel()
{
if (IsActive()) {
-#ifdef USE_EPOLL
active = false;
loop.CancelTimer(*this);
-#else
- g_source_destroy(source);
- g_source_unref(source);
- source = nullptr;
-#endif
}
}
void
+
TimeoutMonitor::Schedule(unsigned ms)
{
Cancel();
-#ifdef USE_EPOLL
active = true;
loop.AddTimer(*this, ms);
-#else
- source = loop.AddTimeout(ms, Callback, this);
-#endif
}
void
@@ -54,33 +45,12 @@ TimeoutMonitor::ScheduleSeconds(unsigned s)
{
Cancel();
-#ifdef USE_EPOLL
Schedule(s * 1000u);
-#else
- source = loop.AddTimeoutSeconds(s, Callback, this);
-#endif
}
void
TimeoutMonitor::Run()
{
-#ifdef USE_EPOLL
active = false;
-#else
- Cancel();
-#endif
-
OnTimeout();
}
-
-#ifndef USE_EPOLL
-
-gboolean
-TimeoutMonitor::Callback(gpointer data)
-{
- TimeoutMonitor &monitor = *(TimeoutMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx
index 98e4e5564..414d48aa6 100644
--- a/src/event/TimeoutMonitor.hxx
+++ b/src/event/TimeoutMonitor.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,34 +22,27 @@
#include "check.h"
-#ifndef USE_EPOLL
-#include <glib.h>
-#endif
-
class EventLoop;
+/**
+ * This class monitors a timeout. Use Schedule() to begin the timeout
+ * or Cancel() to cancel it.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
+ */
class TimeoutMonitor {
-#ifdef USE_EPOLL
friend class EventLoop;
-#endif
EventLoop &loop;
-#ifdef USE_EPOLL
bool active;
-#else
- GSource *source;
-#endif
public:
-#ifdef USE_EPOLL
TimeoutMonitor(EventLoop &_loop)
:loop(_loop), active(false) {
}
-#else
- TimeoutMonitor(EventLoop &_loop)
- :loop(_loop), source(nullptr) {}
-#endif
~TimeoutMonitor() {
Cancel();
@@ -60,11 +53,7 @@ public:
}
bool IsActive() const {
-#ifdef USE_EPOLL
return active;
-#else
- return source != nullptr;
-#endif
}
void Schedule(unsigned ms);
@@ -76,10 +65,6 @@ protected:
private:
void Run();
-
-#ifndef USE_EPOLL
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/WakeFD.hxx b/src/event/WakeFD.hxx
index ed1baafd8..c6222b59c 100644
--- a/src/event/WakeFD.hxx
+++ b/src/event/WakeFD.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx
deleted file mode 100644
index 918a16e53..000000000
--- a/src/filter/AutoConvertFilterPlugin.cxx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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, Error &error) override;
- virtual void Close() override;
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r,
- Error &error) override;
-};
-
-AudioFormat
-AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
-{
- assert(in_audio_format.IsValid());
-
- /* open the "real" filter */
-
- AudioFormat child_audio_format = in_audio_format;
- AudioFormat out_audio_format = filter->Open(child_audio_format, error);
- 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);
- if (convert == nullptr) {
- filter->Close();
- return AudioFormat::Undefined();
- }
-
- AudioFormat audio_format2 = in_audio_format;
- AudioFormat audio_format3 =
- convert->Open(audio_format2, error);
- 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, Error &error)
-{
- if (convert != nullptr) {
- src = convert->FilterPCM(src, src_size, &src_size, error);
- if (src == nullptr)
- return nullptr;
- }
-
- return filter->FilterPCM(src, src_size, dest_size_r, error);
-}
-
-Filter *
-autoconvert_filter_new(Filter *filter)
-{
- return new AutoConvertFilter(filter);
-}
diff --git a/src/filter/AutoConvertFilterPlugin.hxx b/src/filter/AutoConvertFilterPlugin.hxx
deleted file mode 100644
index 7db72a345..000000000
--- a/src/filter/AutoConvertFilterPlugin.hxx
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index b000d53ce..000000000
--- a/src/filter/ChainFilterPlugin.cxx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ChainFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "AudioFormat.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#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 methods from class Filter */
- AudioFormat Open(AudioFormat &af, Error &error) override;
- void Close() override;
- const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error) override;
-
-private:
- /**
- * Close all filters in the chain until #until is reached.
- * #until itself is not closed.
- */
- void CloseUntil(const Filter *until);
-};
-
-static constexpr Domain chain_filter_domain("chain_filter");
-
-static Filter *
-chain_filter_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- 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,
- Error &error)
-{
- AudioFormat conv_audio_format = prev_audio_format;
- const AudioFormat next_audio_format =
- filter->Open(conv_audio_format, error);
- if (!next_audio_format.IsDefined())
- return next_audio_format;
-
- if (conv_audio_format != prev_audio_format) {
- struct audio_format_string s;
-
- filter->Close();
-
- error.Format(chain_filter_domain,
- "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, Error &error)
-{
- AudioFormat audio_format = in_audio_format;
-
- for (auto &child : children) {
- audio_format = chain_open_child(child.name, child.filter,
- audio_format, error);
- 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, Error &error)
-{
- 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);
- if (src == nullptr)
- return nullptr;
- }
-
- /* 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
deleted file mode 100644
index 884c7ca19..000000000
--- a/src/filter/ChainFilterPlugin.hxx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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
deleted file mode 100644
index 040f8426f..000000000
--- a/src/filter/ConvertFilterPlugin.cxx
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ConvertFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#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, Error &error) override;
- virtual void Close() override;
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r,
- Error &error) override;
-};
-
-static Filter *
-convert_filter_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- return new ConvertFilter();
-}
-
-AudioFormat
-ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
-{
- 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, Error &error)
-{
- 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);
-}
-
-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
deleted file mode 100644
index c814aaf49..000000000
--- a/src/filter/ConvertFilterPlugin.hxx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/FilterConfig.cxx b/src/filter/FilterConfig.cxx
new file mode 100644
index 000000000..d8c1fc6c2
--- /dev/null
+++ b/src/filter/FilterConfig.cxx
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FilterConfig.hxx"
+#include "plugins/ChainFilterPlugin.hxx"
+#include "FilterPlugin.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigOption.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+
+#include <algorithm>
+
+#include <string.h>
+
+static bool
+filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
+{
+ const struct config_param *cfg =
+ config_find_block(CONF_AUDIO_FILTER, "name", template_name);
+ if (cfg == nullptr) {
+ error.Format(config_domain,
+ "filter template not found: %s",
+ template_name);
+ return false;
+ }
+
+ // Instantiate one of those filter plugins with the template name as a hint
+ Filter *f = filter_configured_new(*cfg, error);
+ if (f == nullptr)
+ // The error has already been set, just stop.
+ return false;
+
+ const char *plugin_name = cfg->GetBlockValue("plugin",
+ "unknown");
+ filter_chain_append(chain, plugin_name, f);
+
+ return true;
+}
+
+bool
+filter_chain_parse(Filter &chain, const char *spec, Error &error)
+{
+ const char *const end = spec + strlen(spec);
+
+ while (true) {
+ const char *comma = std::find(spec, end, ',');
+ if (comma > spec) {
+ const std::string name(spec, comma);
+ if (!filter_chain_append_new(chain, name.c_str(),
+ error))
+ return false;
+ }
+
+ if (comma == end)
+ break;
+
+ spec = comma + 1;
+ }
+
+ return true;
+}
diff --git a/src/filter/FilterConfig.hxx b/src/filter/FilterConfig.hxx
new file mode 100644
index 000000000..1018eed51
--- /dev/null
+++ b/src/filter/FilterConfig.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Utility functions for filter configuration
+ */
+
+#ifndef MPD_FILTER_CONFIG_HXX
+#define MPD_FILTER_CONFIG_HXX
+
+class Filter;
+class Error;
+
+/**
+ * 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 true on success
+ */
+bool
+filter_chain_parse(Filter &chain, const char *spec, Error &error);
+
+#endif
diff --git a/src/filter/FilterInternal.hxx b/src/filter/FilterInternal.hxx
new file mode 100644
index 000000000..d2e619540
--- /dev/null
+++ b/src/filter/FilterInternal.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Internal stuff for the filter core and filter plugins.
+ */
+
+#ifndef MPD_FILTER_INTERNAL_HXX
+#define MPD_FILTER_INTERNAL_HXX
+
+#include <stddef.h>
+
+struct AudioFormat;
+class Error;
+template<typename T> struct ConstBuffer;
+
+class Filter {
+public:
+ virtual ~Filter() {}
+
+ /**
+ * Opens the filter, preparing it for FilterPCM().
+ *
+ * @param filter the filter object
+ * @param af 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 nullptr
+ * to ignore errors.
+ * @return the format of outgoing data or
+ * AudioFormat::Undefined() on error
+ */
+ virtual AudioFormat Open(AudioFormat &af, Error &error) = 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 error location to store the error occurring, or nullptr
+ * to ignore errors.
+ * @return the destination buffer on success (will be
+ * invalidated by Close() or FilterPCM()), nullptr on
+ * error
+ */
+ virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, Error &error) = 0;
+};
+
+#endif
diff --git a/src/filter/FilterPlugin.cxx b/src/filter/FilterPlugin.cxx
new file mode 100644
index 000000000..98314f771
--- /dev/null
+++ b/src/filter/FilterPlugin.cxx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FilterPlugin.hxx"
+#include "FilterRegistry.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+Filter *
+filter_new(const struct filter_plugin *plugin,
+ const config_param &param, Error &error)
+{
+ assert(plugin != nullptr);
+ assert(!error.IsDefined());
+
+ return plugin->init(param, error);
+}
+
+Filter *
+filter_configured_new(const config_param &param, Error &error)
+{
+ assert(!error.IsDefined());
+
+ const char *plugin_name = param.GetBlockValue("plugin");
+ if (plugin_name == nullptr) {
+ error.Set(config_domain, "No filter plugin specified");
+ return nullptr;
+ }
+
+ const filter_plugin *plugin = filter_plugin_by_name(plugin_name);
+ if (plugin == nullptr) {
+ error.Format(config_domain,
+ "No such filter plugin: %s", plugin_name);
+ return nullptr;
+ }
+
+ return filter_new(plugin, param, error);
+}
diff --git a/src/filter/FilterPlugin.hxx b/src/filter/FilterPlugin.hxx
new file mode 100644
index 000000000..443d29881
--- /dev/null
+++ b/src/filter/FilterPlugin.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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
+
+struct config_param;
+class Filter;
+class Error;
+
+struct filter_plugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a filter.
+ */
+ Filter *(*init)(const config_param &param, Error &error);
+};
+
+/**
+ * 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 nullptr to
+ * ignore errors.
+ * @return a new filter object, or nullptr on error
+ */
+Filter *
+filter_new(const struct filter_plugin *plugin,
+ const config_param &param, Error &error);
+
+/**
+ * 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 nullptr to
+ * ignore errors.
+ * @return a new filter object, or nullptr on error
+ */
+Filter *
+filter_configured_new(const config_param &param, Error &error);
+
+#endif
diff --git a/src/filter/FilterRegistry.cxx b/src/filter/FilterRegistry.cxx
new file mode 100644
index 000000000..286fb8db3
--- /dev/null
+++ b/src/filter/FilterRegistry.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FilterRegistry.hxx"
+#include "FilterPlugin.hxx"
+
+#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,
+ nullptr,
+};
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name)
+{
+ for (unsigned i = 0; filter_plugins[i] != nullptr; ++i)
+ if (strcmp(filter_plugins[i]->name, name) == 0)
+ return filter_plugins[i];
+
+ return nullptr;
+}
diff --git a/src/filter/FilterRegistry.hxx b/src/filter/FilterRegistry.hxx
new file mode 100644
index 000000000..24618a87a
--- /dev/null
+++ b/src/filter/FilterRegistry.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This library manages all filter plugins which are enabled at
+ * compile time.
+ */
+
+#ifndef MPD_FILTER_REGISTRY_HXX
+#define MPD_FILTER_REGISTRY_HXX
+
+#include "Compiler.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;
+
+gcc_pure
+const struct filter_plugin *
+filter_plugin_by_name(const char *name);
+
+#endif
diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx
deleted file mode 100644
index 60d0f3204..000000000
--- a/src/filter/NormalizeFilterPlugin.cxx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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 methods from class Filter */
- AudioFormat Open(AudioFormat &af, Error &error) override;
- void Close() override;
- const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error) override;
-};
-
-static Filter *
-normalize_filter_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- return new NormalizeFilter();
-}
-
-AudioFormat
-NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
-{
- 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 Error &error)
-{
- 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
deleted file mode 100644
index c762592f6..000000000
--- a/src/filter/NullFilterPlugin.cxx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 "Compiler.h"
-
-class NullFilter final : public Filter {
-public:
- virtual AudioFormat Open(AudioFormat &af,
- gcc_unused Error &error) override {
- return af;
- }
-
- virtual void Close() override {}
-
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r,
- gcc_unused Error &error) override {
- *dest_size_r = src_size;
- return src;
- }
-};
-
-static Filter *
-null_filter_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- 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
deleted file mode 100644
index b79b4fb87..000000000
--- a/src/filter/ReplayGainFilterPlugin.cxx
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ReplayGainFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "AudioFormat.hxx"
-#include "ReplayGainInfo.hxx"
-#include "ReplayGainConfig.hxx"
-#include "MixerControl.hxx"
-#include "pcm/PcmVolume.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain replay_gain_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;
-
- ReplayGainMode mode;
-
- ReplayGainInfo 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) {
- info.Clear();
- }
-
- void SetMixer(Mixer *_mixer, unsigned _base) {
- assert(_mixer == nullptr || (_base > 0 && _base <= 100));
-
- mixer = _mixer;
- base = _base;
-
- Update();
- }
-
- void SetInfo(const ReplayGainInfo *_info) {
- if (_info != nullptr) {
- info = *_info;
- info.Complete();
- } else
- info.Clear();
-
- Update();
- }
-
- void SetMode(ReplayGainMode _mode) {
- if (_mode == mode)
- /* no change */
- return;
-
- FormatDebug(replay_gain_domain,
- "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 methods from class Filter */
- AudioFormat Open(AudioFormat &af, Error &error) override;
- void Close() override;
- const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error) override;
-};
-
-void
-ReplayGainFilter::Update()
-{
- if (mode != REPLAY_GAIN_OFF) {
- const auto &tuple = info.tuples[mode];
- float scale = tuple.CalculateScale(replay_gain_preamp,
- replay_gain_missing_preamp,
- replay_gain_limit);
- FormatDebug(replay_gain_domain,
- "scale=%f\n", (double)scale);
-
- volume = pcm_float_to_volume(scale);
- } else
- volume = PCM_VOLUME_1;
-
- if (mixer != nullptr) {
- /* update the hardware mixer volume */
-
- unsigned _volume = (volume * base) / PCM_VOLUME_1;
- if (_volume > 100)
- _volume = 100;
-
- Error error;
- if (!mixer_set_volume(mixer, _volume, error))
- LogError(error, "Failed to update hardware mixer");
- }
-}
-
-static Filter *
-replay_gain_filter_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- return new ReplayGainFilter();
-}
-
-AudioFormat
-ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
-{
- 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, Error &error)
-{
-
- *dest_size_r = src_size;
-
- if (volume == PCM_VOLUME_1)
- /* optimized special case: 100% volume = no-op */
- return src;
-
- void *dest = buffer.Get(src_size);
- if (volume <= 0) {
- /* optimized special case: 0% volume = memset(0) */
- /* XXX is this valid for all sample formats? What
- about floating point? */
- memset(dest, 0, src_size);
- return dest;
- }
-
- memcpy(dest, src, src_size);
-
- bool success = pcm_volume(dest, src_size,
- format.format,
- volume);
- if (!success) {
- error.Set(replay_gain_domain, "pcm_volume() has failed");
- return nullptr;
- }
-
- return dest;
-}
-
-const 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 ReplayGainInfo *info)
-{
- ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
-
- filter->SetInfo(info);
-}
-
-void
-replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode)
-{
- ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
-
- filter->SetMode(mode);
-}
diff --git a/src/filter/ReplayGainFilterPlugin.hxx b/src/filter/ReplayGainFilterPlugin.hxx
deleted file mode 100644
index fbd1f2712..000000000
--- a/src/filter/ReplayGainFilterPlugin.hxx
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
-#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
-
-#include "ReplayGainInfo.hxx"
-
-class Filter;
-class Mixer;
-
-/**
- * Enables or disables the hardware mixer for applying replay gain.
- *
- * @param mixer the hardware mixer, or nullptr 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 nullptr if no replay
- * gain data is available for the current song
- */
-void
-replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info);
-
-void
-replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode);
-
-#endif
diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx
deleted file mode 100644
index 335cfe6bd..000000000
--- a/src/filter/RouteFilterPlugin.cxx
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 "ConfigError.hxx"
-#include "ConfigData.hxx"
-#include "AudioFormat.hxx"
-#include "CheckAudioFormat.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "util/StringUtil.hxx"
-#include "util/Error.hxx"
-
-#include <algorithm>
-
-#include <assert.h>
-#include <string.h>
-#include <stdint.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 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 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"
- */
- int8_t sources[MAX_CHANNELS];
-
- /**
- * 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:
- /**
- * 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, Error &error);
-
- /* virtual methods from class Filter */
- AudioFormat Open(AudioFormat &af, Error &error) override;
- void Close() override;
- const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error) override;
-};
-
-bool
-RouteFilter::Configure(const config_param &param, Error &error) {
-
- /* 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 realloc() instead of one count run and one malloc().
- */
-
- std::fill_n(sources, MAX_CHANNELS, -1);
-
- min_input_channels = 0;
- min_output_channels = 0;
-
- // A cowardly default, just passthrough stereo
- const char *routes = param.GetBlockValue("routes", "0>0, 1>1");
- while (true) {
- routes = strchug_fast(routes);
-
- char *endptr;
- const unsigned source = strtoul(routes, &endptr, 10);
- endptr = strchug_fast(endptr);
- if (endptr == routes || *endptr != '>') {
- error.Set(config_domain,
- "Malformed 'routes' specification");
- return false;
- }
-
- if (source >= MAX_CHANNELS) {
- error.Format(config_domain,
- "Invalid source channel number: %u",
- source);
- return false;
- }
-
- if (source >= min_input_channels)
- min_input_channels = source + 1;
-
- routes = strchug_fast(endptr + 1);
-
- unsigned dest = strtoul(routes, &endptr, 10);
- endptr = strchug_fast(endptr);
- if (endptr == routes) {
- error.Set(config_domain,
- "Malformed 'routes' specification");
- return false;
- }
-
- if (dest >= MAX_CHANNELS) {
- error.Format(config_domain,
- "Invalid destination channel number: %u",
- dest);
- return false;
- }
-
- if (dest >= min_output_channels)
- min_output_channels = dest + 1;
-
- sources[dest] = source;
-
- routes = endptr;
-
- if (*routes == 0)
- break;
-
- if (*routes != ',') {
- error.Set(config_domain,
- "Malformed 'routes' specification");
- return false;
- }
-
- ++routes;
- }
-
- return true;
-}
-
-static Filter *
-route_filter_init(const config_param &param, Error &error)
-{
- RouteFilter *filter = new RouteFilter();
- if (!filter->Configure(param, error)) {
- delete filter;
- return nullptr;
- }
-
- return filter;
-}
-
-AudioFormat
-RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
-{
- // 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 Error &error)
-{
- 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;
-
- // Grow our reusable buffer, if needed, and set the moving pointer
- *dest_size_r = number_of_frames * output_frame_size;
- void *const result = output_buffer.Get(*dest_size_r);
-
- // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
- uint8_t *chan_destination = (uint8_t *)result;
-
- // 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 result;
-}
-
-const struct filter_plugin route_filter_plugin = {
- "route",
- route_filter_init,
-};
diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx
deleted file mode 100644
index 66afdea88..000000000
--- a/src/filter/VolumeFilterPlugin.cxx
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "VolumeFilterPlugin.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "pcm/PcmVolume.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "AudioFormat.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.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;
- }
-
- AudioFormat Open(AudioFormat &af, Error &error) override;
- void Close() override;
- const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error) override;
-};
-
-static constexpr Domain volume_domain("pcm_volume");
-
-static Filter *
-volume_filter_init(gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- return new VolumeFilter();
-}
-
-AudioFormat
-VolumeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
-{
- 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, Error &error)
-{
- *dest_size_r = src_size;
-
- if (volume >= PCM_VOLUME_1)
- /* optimized special case: 100% volume = no-op */
- return src;
-
- void *dest = buffer.Get(src_size);
-
- if (volume <= 0) {
- /* optimized special case: 0% volume = memset(0) */
- /* XXX is this valid for all sample formats? What
- about floating point? */
- memset(dest, 0, src_size);
- return dest;
- }
-
- memcpy(dest, src, src_size);
-
- bool success = pcm_volume(dest, src_size,
- format.format,
- volume);
- if (!success) {
- error.Set(volume_domain, "pcm_volume() has failed");
- return NULL;
- }
-
- return dest;
-}
-
-const 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
deleted file mode 100644
index 822b7e93a..000000000
--- a/src/filter/VolumeFilterPlugin.hxx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx
new file mode 100644
index 000000000..8586cb86e
--- /dev/null
+++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AutoConvertFilterPlugin.hxx"
+#include "ConvertFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigData.hxx"
+#include "util/ConstBuffer.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, Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+AudioFormat
+AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
+{
+ assert(in_audio_format.IsValid());
+
+ /* open the "real" filter */
+
+ AudioFormat child_audio_format = in_audio_format;
+ AudioFormat out_audio_format = filter->Open(child_audio_format, error);
+ 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);
+ if (convert == nullptr) {
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ AudioFormat audio_format2 = in_audio_format;
+ AudioFormat audio_format3 =
+ convert->Open(audio_format2, error);
+ if (!audio_format3.IsDefined()) {
+ delete convert;
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ assert(audio_format2 == in_audio_format);
+
+ if (!convert_filter_set(convert, child_audio_format, error)) {
+ delete convert;
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+ } else
+ /* no */
+ convert = nullptr;
+
+ return out_audio_format;
+}
+
+void
+AutoConvertFilter::Close()
+{
+ if (convert != nullptr) {
+ convert->Close();
+ delete convert;
+ }
+
+ filter->Close();
+}
+
+ConstBuffer<void>
+AutoConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error)
+{
+ if (convert != nullptr) {
+ src = convert->FilterPCM(src, error);
+ if (src.IsNull())
+ return nullptr;
+ }
+
+ return filter->FilterPCM(src, error);
+}
+
+Filter *
+autoconvert_filter_new(Filter *filter)
+{
+ return new AutoConvertFilter(filter);
+}
diff --git a/src/filter/plugins/AutoConvertFilterPlugin.hxx b/src/filter/plugins/AutoConvertFilterPlugin.hxx
new file mode 100644
index 000000000..c5dfdd2f6
--- /dev/null
+++ b/src/filter/plugins/AutoConvertFilterPlugin.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx
new file mode 100644
index 000000000..4aeee69af
--- /dev/null
+++ b/src/filter/plugins/ChainFilterPlugin.cxx
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ChainFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/ConstBuffer.hxx"
+
+#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 methods from class Filter */
+ AudioFormat Open(AudioFormat &af, Error &error) override;
+ void Close() override;
+ ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+
+private:
+ /**
+ * Close all filters in the chain until #until is reached.
+ * #until itself is not closed.
+ */
+ void CloseUntil(const Filter *until);
+};
+
+static constexpr Domain chain_filter_domain("chain_filter");
+
+static Filter *
+chain_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ 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,
+ Error &error)
+{
+ AudioFormat conv_audio_format = prev_audio_format;
+ const AudioFormat next_audio_format =
+ filter->Open(conv_audio_format, error);
+ if (!next_audio_format.IsDefined())
+ return next_audio_format;
+
+ if (conv_audio_format != prev_audio_format) {
+ struct audio_format_string s;
+
+ filter->Close();
+
+ error.Format(chain_filter_domain,
+ "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, Error &error)
+{
+ AudioFormat audio_format = in_audio_format;
+
+ for (auto &child : children) {
+ audio_format = chain_open_child(child.name, child.filter,
+ audio_format, error);
+ 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();
+}
+
+ConstBuffer<void>
+ChainFilter::FilterPCM(ConstBuffer<void> src, Error &error)
+{
+ for (auto &child : children) {
+ /* feed the output of the previous filter as input
+ into the current one */
+ src = child.filter->FilterPCM(src, error);
+ if (src.IsNull())
+ return nullptr;
+ }
+
+ /* return the output of the last filter */
+ 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/plugins/ChainFilterPlugin.hxx b/src/filter/plugins/ChainFilterPlugin.hxx
new file mode 100644
index 000000000..b36aa3322
--- /dev/null
+++ b/src/filter/plugins/ChainFilterPlugin.hxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx
new file mode 100644
index 000000000..5c6a07ba1
--- /dev/null
+++ b/src/filter/plugins/ConvertFilterPlugin.cxx
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConvertFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "util/Manual.hxx"
+#include "util/ConstBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "poison.h"
+
+#include <assert.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.
+ *
+ * If this is AudioFormat::Undefined(), then the #PcmConvert
+ * attribute is not open. This can mean that Set() has failed
+ * or that no conversion is necessary.
+ */
+ AudioFormat out_audio_format;
+
+ Manual<PcmConvert> state;
+
+public:
+ bool Set(const AudioFormat &_out_audio_format, Error &error);
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+static Filter *
+convert_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new ConvertFilter();
+}
+
+bool
+ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error)
+{
+ assert(in_audio_format.IsValid());
+ assert(_out_audio_format.IsValid());
+
+ if (_out_audio_format == out_audio_format)
+ /* no change */
+ return true;
+
+ if (out_audio_format.IsValid()) {
+ out_audio_format.Clear();
+ state->Close();
+ }
+
+ if (_out_audio_format == in_audio_format)
+ /* optimized special case: no-op */
+ return true;
+
+ if (!state->Open(in_audio_format, _out_audio_format, error))
+ return false;
+
+ out_audio_format = _out_audio_format;
+ return true;
+}
+
+AudioFormat
+ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ assert(audio_format.IsValid());
+
+ in_audio_format = audio_format;
+ out_audio_format.Clear();
+
+ state.Construct();
+
+ return in_audio_format;
+}
+
+void
+ConvertFilter::Close()
+{
+ assert(in_audio_format.IsValid());
+
+ if (out_audio_format.IsValid())
+ state->Close();
+
+ state.Destruct();
+
+ poison_undefined(&in_audio_format, sizeof(in_audio_format));
+ poison_undefined(&out_audio_format, sizeof(out_audio_format));
+}
+
+ConstBuffer<void>
+ConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error)
+{
+ assert(in_audio_format.IsValid());
+
+ if (!out_audio_format.IsValid())
+ /* optimized special case: no-op */
+ return src;
+
+ return state->Convert(src, error);
+}
+
+const struct filter_plugin convert_filter_plugin = {
+ "convert",
+ convert_filter_init,
+};
+
+bool
+convert_filter_set(Filter *_filter, AudioFormat out_audio_format,
+ Error &error)
+{
+ ConvertFilter *filter = (ConvertFilter *)_filter;
+
+ return filter->Set(out_audio_format, error);
+}
diff --git a/src/filter/plugins/ConvertFilterPlugin.hxx b/src/filter/plugins/ConvertFilterPlugin.hxx
new file mode 100644
index 000000000..bb4673651
--- /dev/null
+++ b/src/filter/plugins/ConvertFilterPlugin.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX
+#define MPD_CONVERT_FILTER_PLUGIN_HXX
+
+class Filter;
+class Error;
+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.
+ */
+bool
+convert_filter_set(Filter *filter, AudioFormat out_audio_format,
+ Error &error);
+
+#endif
diff --git a/src/filter/plugins/NormalizeFilterPlugin.cxx b/src/filter/plugins/NormalizeFilterPlugin.cxx
new file mode 100644
index 000000000..372ab53ac
--- /dev/null
+++ b/src/filter/plugins/NormalizeFilterPlugin.cxx
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "AudioCompress/compress.h"
+#include "util/ConstBuffer.hxx"
+
+#include <string.h>
+
+class NormalizeFilter final : public Filter {
+ struct Compressor *compressor;
+
+ PcmBuffer buffer;
+
+public:
+ /* virtual methods from class Filter */
+ AudioFormat Open(AudioFormat &af, Error &error) override;
+ void Close() override;
+ ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+static Filter *
+normalize_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new NormalizeFilter();
+}
+
+AudioFormat
+NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ audio_format.format = SampleFormat::S16;
+
+ compressor = Compressor_new(0);
+
+ return audio_format;
+}
+
+void
+NormalizeFilter::Close()
+{
+ buffer.Clear();
+ Compressor_delete(compressor);
+}
+
+ConstBuffer<void>
+NormalizeFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ int16_t *dest = (int16_t *)buffer.Get(src.size);
+ memcpy(dest, src.data, src.size);
+
+ Compressor_Process_int16(compressor, dest, src.size / 2);
+ return { (const void *)dest, src.size };
+}
+
+const struct filter_plugin normalize_filter_plugin = {
+ "normalize",
+ normalize_filter_init,
+};
diff --git a/src/filter/plugins/NullFilterPlugin.cxx b/src/filter/plugins/NullFilterPlugin.cxx
new file mode 100644
index 000000000..ebd8e4ec5
--- /dev/null
+++ b/src/filter/plugins/NullFilterPlugin.cxx
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "Compiler.h"
+#include "util/ConstBuffer.hxx"
+
+class NullFilter final : public Filter {
+public:
+ virtual AudioFormat Open(AudioFormat &af,
+ gcc_unused Error &error) override {
+ return af;
+ }
+
+ virtual void Close() override {}
+
+ virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ gcc_unused Error &error) override {
+ return src;
+ }
+};
+
+static Filter *
+null_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new NullFilter();
+}
+
+const struct filter_plugin null_filter_plugin = {
+ "null",
+ null_filter_init,
+};
diff --git a/src/filter/plugins/ReplayGainFilterPlugin.cxx b/src/filter/plugins/ReplayGainFilterPlugin.cxx
new file mode 100644
index 000000000..f76e48e37
--- /dev/null
+++ b/src/filter/plugins/ReplayGainFilterPlugin.cxx
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ReplayGainFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "ReplayGainInfo.hxx"
+#include "ReplayGainConfig.hxx"
+#include "mixer/MixerControl.hxx"
+#include "pcm/Volume.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+static constexpr Domain replay_gain_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;
+
+ ReplayGainMode mode;
+
+ ReplayGainInfo info;
+
+ /**
+ * About the current volume: it is between 0 and a value that
+ * may or may not exceed #PCM_VOLUME_1.
+ *
+ * If the default value of true is used for replaygain_limit, the
+ * application of the volume to the signal will never cause clipping.
+ *
+ * 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.
+ */
+ PcmVolume pv;
+
+public:
+ ReplayGainFilter()
+ :mixer(nullptr), mode(REPLAY_GAIN_OFF) {
+ info.Clear();
+ }
+
+ void SetMixer(Mixer *_mixer, unsigned _base) {
+ assert(_mixer == nullptr || (_base > 0 && _base <= 100));
+
+ mixer = _mixer;
+ base = _base;
+
+ Update();
+ }
+
+ void SetInfo(const ReplayGainInfo *_info) {
+ if (_info != nullptr) {
+ info = *_info;
+ info.Complete();
+ } else
+ info.Clear();
+
+ Update();
+ }
+
+ void SetMode(ReplayGainMode _mode) {
+ if (_mode == mode)
+ /* no change */
+ return;
+
+ FormatDebug(replay_gain_domain,
+ "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 methods from class Filter */
+ AudioFormat Open(AudioFormat &af, Error &error) override;
+ void Close() override;
+ ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+void
+ReplayGainFilter::Update()
+{
+ unsigned volume = PCM_VOLUME_1;
+ if (mode != REPLAY_GAIN_OFF) {
+ const auto &tuple = info.tuples[mode];
+ float scale = tuple.CalculateScale(replay_gain_preamp,
+ replay_gain_missing_preamp,
+ replay_gain_limit);
+ FormatDebug(replay_gain_domain,
+ "scale=%f\n", (double)scale);
+
+ volume = pcm_float_to_volume(scale);
+ }
+
+ pv.SetVolume(volume);
+
+ if (mixer != nullptr) {
+ /* update the hardware mixer volume */
+
+ unsigned _volume = (volume * base) / PCM_VOLUME_1;
+ if (_volume > 100)
+ _volume = 100;
+
+ Error error;
+ if (!mixer_set_volume(mixer, _volume, error))
+ LogError(error, "Failed to update hardware mixer");
+ }
+}
+
+static Filter *
+replay_gain_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new ReplayGainFilter();
+}
+
+AudioFormat
+ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
+{
+ if (!pv.Open(af.format, error))
+ return AudioFormat::Undefined();
+
+ return af;
+}
+
+void
+ReplayGainFilter::Close()
+{
+ pv.Close();
+}
+
+ConstBuffer<void>
+ReplayGainFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ return pv.Apply(src);
+}
+
+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 ReplayGainInfo *info)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetInfo(info);
+}
+
+void
+replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetMode(mode);
+}
diff --git a/src/filter/plugins/ReplayGainFilterPlugin.hxx b/src/filter/plugins/ReplayGainFilterPlugin.hxx
new file mode 100644
index 000000000..346541b97
--- /dev/null
+++ b/src/filter/plugins/ReplayGainFilterPlugin.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
+#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
+
+#include "ReplayGainInfo.hxx"
+
+class Filter;
+class Mixer;
+
+/**
+ * Enables or disables the hardware mixer for applying replay gain.
+ *
+ * @param mixer the hardware mixer, or nullptr 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 nullptr if no replay
+ * gain data is available for the current song
+ */
+void
+replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info);
+
+void
+replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode);
+
+#endif
diff --git a/src/filter/plugins/RouteFilterPlugin.cxx b/src/filter/plugins/RouteFilterPlugin.cxx
new file mode 100644
index 000000000..4094119f2
--- /dev/null
+++ b/src/filter/plugins/RouteFilterPlugin.cxx
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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 "config/ConfigError.hxx"
+#include "config/ConfigData.hxx"
+#include "AudioFormat.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+#include <stdint.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 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 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"
+ */
+ int8_t sources[MAX_CHANNELS];
+
+ /**
+ * 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:
+ /**
+ * 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, Error &error);
+
+ /* virtual methods from class Filter */
+ AudioFormat Open(AudioFormat &af, Error &error) override;
+ void Close() override;
+ ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+bool
+RouteFilter::Configure(const config_param &param, Error &error) {
+
+ /* 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 realloc() instead of one count run and one malloc().
+ */
+
+ std::fill_n(sources, MAX_CHANNELS, -1);
+
+ min_input_channels = 0;
+ min_output_channels = 0;
+
+ // A cowardly default, just passthrough stereo
+ const char *routes = param.GetBlockValue("routes", "0>0, 1>1");
+ while (true) {
+ routes = StripLeft(routes);
+
+ char *endptr;
+ const unsigned source = strtoul(routes, &endptr, 10);
+ endptr = StripLeft(endptr);
+ if (endptr == routes || *endptr != '>') {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ if (source >= MAX_CHANNELS) {
+ error.Format(config_domain,
+ "Invalid source channel number: %u",
+ source);
+ return false;
+ }
+
+ if (source >= min_input_channels)
+ min_input_channels = source + 1;
+
+ routes = StripLeft(endptr + 1);
+
+ unsigned dest = strtoul(routes, &endptr, 10);
+ endptr = StripLeft(endptr);
+ if (endptr == routes) {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ if (dest >= MAX_CHANNELS) {
+ error.Format(config_domain,
+ "Invalid destination channel number: %u",
+ dest);
+ return false;
+ }
+
+ if (dest >= min_output_channels)
+ min_output_channels = dest + 1;
+
+ sources[dest] = source;
+
+ routes = endptr;
+
+ if (*routes == 0)
+ break;
+
+ if (*routes != ',') {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ ++routes;
+ }
+
+ return true;
+}
+
+static Filter *
+route_filter_init(const config_param &param, Error &error)
+{
+ RouteFilter *filter = new RouteFilter();
+ if (!filter->Configure(param, error)) {
+ delete filter;
+ return nullptr;
+ }
+
+ return filter;
+}
+
+AudioFormat
+RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ // 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();
+}
+
+ConstBuffer<void>
+RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ 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.data;
+
+ // Grow our reusable buffer, if needed, and set the moving pointer
+ const size_t result_size = number_of_frames * output_frame_size;
+ void *const result = output_buffer.Get(result_size);
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
+ uint8_t *chan_destination = (uint8_t *)result;
+
+ // 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 { result, result_size };
+}
+
+const struct filter_plugin route_filter_plugin = {
+ "route",
+ route_filter_init,
+};
diff --git a/src/filter/plugins/VolumeFilterPlugin.cxx b/src/filter/plugins/VolumeFilterPlugin.cxx
new file mode 100644
index 000000000..17e061476
--- /dev/null
+++ b/src/filter/plugins/VolumeFilterPlugin.cxx
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VolumeFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/Volume.hxx"
+#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+class VolumeFilter final : public Filter {
+ PcmVolume pv;
+
+public:
+ unsigned GetVolume() const {
+ return pv.GetVolume();
+ }
+
+ void SetVolume(unsigned _volume) {
+ pv.SetVolume(_volume);
+ }
+
+ /* virtual methods from class Filter */
+ AudioFormat Open(AudioFormat &af, Error &error) override;
+ void Close() override;
+ ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+static constexpr Domain volume_domain("pcm_volume");
+
+static Filter *
+volume_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new VolumeFilter();
+}
+
+AudioFormat
+VolumeFilter::Open(AudioFormat &audio_format, Error &error)
+{
+ if (!pv.Open(audio_format.format, error))
+ return AudioFormat::Undefined();
+
+ return audio_format;
+}
+
+void
+VolumeFilter::Close()
+{
+ pv.Close();
+}
+
+ConstBuffer<void>
+VolumeFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ return pv.Apply(src);
+}
+
+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/plugins/VolumeFilterPlugin.hxx b/src/filter/plugins/VolumeFilterPlugin.hxx
new file mode 100644
index 000000000..b5317dc6f
--- /dev/null
+++ b/src/filter/plugins/VolumeFilterPlugin.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx
index 4651ea625..ceaad73ea 100644
--- a/src/fs/AllocatedPath.cxx
+++ b/src/fs/AllocatedPath.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,33 +24,36 @@
#include "util/Error.hxx"
#include "Compiler.h"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
-#include <assert.h>
#include <string.h>
+#ifdef HAVE_GLIB
+
inline AllocatedPath::AllocatedPath(Donate, pointer _value)
:value(_value) {
g_free(_value);
}
+#endif
+
/* no inlining, please */
AllocatedPath::~AllocatedPath() {}
AllocatedPath
-AllocatedPath::Build(const_pointer a, const_pointer b)
-{
- return AllocatedPath(Donate(), g_build_filename(a, b, nullptr));
-}
-
-AllocatedPath
AllocatedPath::FromUTF8(const char *path_utf8)
{
+#ifdef HAVE_GLIB
char *path = ::PathFromUTF8(path_utf8);
if (path == nullptr)
return AllocatedPath::Null();
return AllocatedPath(Donate(), path);
+#else
+ return FromFS(path_utf8);
+#endif
}
AllocatedPath
@@ -68,7 +71,7 @@ AllocatedPath::FromUTF8(const char *path_utf8, Error &error)
AllocatedPath
AllocatedPath::GetDirectoryName() const
{
- return AllocatedPath(Donate(), g_path_get_dirname(c_str()));
+ return FromFS(PathTraitsFS::GetParent(c_str()));
}
std::string
@@ -86,14 +89,14 @@ AllocatedPath::RelativeFS(const char *other_fs) const
other_fs += l;
if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
+ if (!PathTraitsFS::IsSeparator(*other_fs))
/* mismatch */
return nullptr;
/* skip remaining path separators */
do {
++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
+ } while (PathTraitsFS::IsSeparator(*other_fs));
}
return other_fs;
@@ -105,7 +108,7 @@ AllocatedPath::ChopSeparators()
size_t l = length();
const char *p = data();
- while (l >= 2 && PathTraits::IsSeparatorFS(p[l - 1])) {
+ while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) {
--l;
#if GCC_CHECK_VERSION(4,7) && !defined(__clang__)
diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx
index 36d8a1598..c345470c8 100644
--- a/src/fs/AllocatedPath.hxx
+++ b/src/fs/AllocatedPath.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,8 +28,6 @@
#include <utility>
#include <string>
-#include <assert.h>
-
class Error;
/**
@@ -39,11 +37,10 @@ class Error;
* stored.
*/
class AllocatedPath {
- typedef std::string string;
-
- typedef PathTraits::value_type value_type;
- typedef PathTraits::pointer pointer;
- typedef PathTraits::const_pointer const_pointer;
+ typedef PathTraitsFS::string string;
+ typedef PathTraitsFS::value_type value_type;
+ typedef PathTraitsFS::pointer pointer;
+ typedef PathTraitsFS::const_pointer const_pointer;
string value;
@@ -56,17 +53,25 @@ class AllocatedPath {
AllocatedPath(const_pointer _value):value(_value) {}
+ AllocatedPath(string &&_value):value(std::move(_value)) {}
+
+ static AllocatedPath Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size) {
+ return AllocatedPath(PathTraitsFS::Build(a, a_size, b, b_size));
+ }
public:
/**
- * Copy a #AllocatedPath object.
+ * Copy an #AllocatedPath object.
*/
AllocatedPath(const AllocatedPath &) = default;
/**
- * Move a #AllocatedPath object.
+ * Move an #AllocatedPath object.
*/
AllocatedPath(AllocatedPath &&other):value(std::move(other.value)) {}
+ explicit AllocatedPath(Path other):value(other.c_str()) {}
+
~AllocatedPath();
/**
@@ -89,22 +94,38 @@ public:
* Join two path components with the path separator.
*/
gcc_pure gcc_nonnull_all
- static AllocatedPath Build(const_pointer a, const_pointer b);
+ static AllocatedPath Build(const_pointer a, const_pointer b) {
+ return Build(a, PathTraitsFS::GetLength(a),
+ b, PathTraitsFS::GetLength(b));
+ }
gcc_pure gcc_nonnull_all
- static AllocatedPath Build(const_pointer a, const AllocatedPath &b) {
+ static AllocatedPath Build(Path a, const_pointer b) {
+ return Build(a.c_str(), b);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static AllocatedPath Build(Path a, Path b) {
return Build(a, b.c_str());
}
gcc_pure gcc_nonnull_all
+ static AllocatedPath Build(const_pointer a, const AllocatedPath &b) {
+ return Build(a, PathTraitsFS::GetLength(a),
+ b.value.c_str(), b.value.size());
+ }
+
+ gcc_pure gcc_nonnull_all
static AllocatedPath Build(const AllocatedPath &a, const_pointer b) {
- return Build(a.c_str(), b);
+ return Build(a.value.c_str(), a.value.size(),
+ b, PathTraitsFS::GetLength(b));
}
gcc_pure
static AllocatedPath Build(const AllocatedPath &a,
const AllocatedPath &b) {
- return Build(a.c_str(), b.c_str());
+ return Build(a.value.c_str(), a.value.size(),
+ b.value.c_str(), b.value.size());
}
/**
@@ -117,7 +138,16 @@ public:
}
/**
- * Convert a UTF-8 C string to a #AllocatedPath instance.
+ * Convert a C++ string that is already in the filesystem
+ * character set to a #Path instance.
+ */
+ gcc_pure
+ static AllocatedPath FromFS(string &&fs) {
+ return AllocatedPath(std::move(fs));
+ }
+
+ /**
+ * Convert a UTF-8 C string to an #AllocatedPath instance.
* Returns return a "nulled" instance on error.
*/
gcc_pure gcc_nonnull_all
@@ -127,12 +157,12 @@ public:
static AllocatedPath FromUTF8(const char *path_utf8, Error &error);
/**
- * Copy a #AllocatedPath object.
+ * Copy an #AllocatedPath object.
*/
AllocatedPath &operator=(const AllocatedPath &) = default;
/**
- * Move a #AllocatedPath object.
+ * Move an #AllocatedPath object.
*/
AllocatedPath &operator=(AllocatedPath &&other) {
value = std::move(other.value);
@@ -140,6 +170,14 @@ public:
}
/**
+ * Allows the caller to "steal" the internal value by
+ * providing a rvalue reference to the std::string attribute.
+ */
+ std::string &&Steal() {
+ return std::move(value);
+ }
+
+ /**
* Check if this is a "nulled" instance. A "nulled" instance
* must not be used.
*/
@@ -215,7 +253,7 @@ public:
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx
index 0b598ef46..c634c9340 100644
--- a/src/fs/Charset.cxx
+++ b/src/fs/Charset.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,15 +22,20 @@
#include "Domain.hxx"
#include "Limits.hxx"
#include "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include "Log.hxx"
+#include "Traits.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
+
+#include <algorithm>
#include <assert.h>
#include <string.h>
+#ifdef HAVE_GLIB
+
/**
* Maximal number of bytes required to represent path name in UTF-8
* (including nul-terminator).
@@ -70,10 +75,29 @@ SetFSCharset(const char *charset)
"SetFSCharset: fs charset is: %s", fs_charset.c_str());
}
+#endif
+
const char *
GetFSCharset()
{
- return fs_charset.empty() ? "utf-8" : fs_charset.c_str();
+#ifdef HAVE_GLIB
+ return fs_charset.empty() ? "UTF-8" : fs_charset.c_str();
+#else
+ return "UTF-8";
+#endif
+}
+
+static inline void FixSeparators(std::string &s)
+{
+#ifdef WIN32
+ // For whatever reason GCC can't convert constexpr to value reference.
+ // This leads to link errors when passing separators directly.
+ auto from = PathTraitsFS::SEPARATOR;
+ auto to = PathTraitsUTF8::SEPARATOR;
+ std::replace(s.begin(), s.end(), from, to);
+#else
+ (void)s;
+#endif
}
std::string
@@ -84,8 +108,14 @@ PathToUTF8(const char *path_fs)
assert(path_fs != nullptr);
#endif
- if (fs_charset.empty())
- return std::string(path_fs);
+#ifdef HAVE_GLIB
+ if (fs_charset.empty()) {
+#endif
+ auto result = std::string(path_fs);
+ FixSeparators(result);
+ return result;
+#ifdef HAVE_GLIB
+ }
GIConv conv = g_iconv_open("utf-8", fs_charset.c_str());
if (conv == reinterpret_cast<GIConv>(-1))
@@ -106,9 +136,14 @@ PathToUTF8(const char *path_fs)
if (ret == static_cast<size_t>(-1) || in_left > 0)
return std::string();
- return std::string(path_utf8, sizeof(path_utf8) - out_left);
+ auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left);
+ FixSeparators(result_path);
+ return result_path;
+#endif
}
+#ifdef HAVE_GLIB
+
char *
PathFromUTF8(const char *path_utf8)
{
@@ -124,3 +159,5 @@ PathFromUTF8(const char *path_utf8)
fs_charset.c_str(), "utf-8",
nullptr, nullptr, nullptr);
}
+
+#endif
diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx
index a89cb0459..0a71d7c58 100644
--- a/src/fs/Charset.hxx
+++ b/src/fs/Charset.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/CheckFile.cxx b/src/fs/CheckFile.cxx
new file mode 100644
index 000000000..a35443674
--- /dev/null
+++ b/src/fs/CheckFile.cxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CheckFile.hxx"
+#include "Log.hxx"
+#include "config/ConfigError.hxx"
+#include "FileSystem.hxx"
+#include "Path.hxx"
+#include "AllocatedPath.hxx"
+#include "DirectoryReader.hxx"
+
+#include <errno.h>
+#include <sys/stat.h>
+
+void
+CheckDirectoryReadable(Path path_fs)
+{
+ struct stat st;
+ if (!StatFile(path_fs, st)) {
+ FormatErrno(config_domain,
+ "Failed to stat directory \"%s\"",
+ path_fs.c_str());
+ return;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ FormatError(config_domain,
+ "Not a directory: %s", path_fs.c_str());
+ return;
+ }
+
+#ifndef WIN32
+ const auto x = AllocatedPath::Build(path_fs, ".");
+ if (!StatFile(x, st) && errno == EACCES)
+ FormatError(config_domain,
+ "No permission to traverse (\"execute\") directory: %s",
+ path_fs.c_str());
+#endif
+
+ const DirectoryReader reader(path_fs);
+ if (reader.HasFailed() && errno == EACCES)
+ FormatError(config_domain,
+ "No permission to read directory: %s", path_fs.c_str());
+
+}
diff --git a/src/fs/CheckFile.hxx b/src/fs/CheckFile.hxx
new file mode 100644
index 000000000..00559647d
--- /dev/null
+++ b/src/fs/CheckFile.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FS_CHECK_FILE_HXX
+#define MPD_FS_CHECK_FILE_HXX
+
+#include "check.h"
+
+class Path;
+
+/**
+ * Check whether the directory is readable and usable. Logs a warning
+ * if there is a problem.
+ */
+void
+CheckDirectoryReadable(Path path_fs);
+
+#endif
diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx
index 63e64ef99..6aa23005c 100644
--- a/src/fs/Config.cxx
+++ b/src/fs/Config.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,24 +20,19 @@
#include "config.h"
#include "Config.hxx"
#include "Charset.hxx"
-#include "Domain.hxx"
-#include "ConfigGlobal.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
+#include "config/ConfigGlobal.hxx"
#ifdef WIN32
#include <windows.h> // for GetACP()
#include <stdio.h> // for sprintf()
+#elif defined(HAVE_GLIB)
+#include <glib.h>
#endif
void
ConfigureFS()
{
+#if defined(HAVE_GLIB) || defined(WIN32)
const char *charset = nullptr;
charset = config_get_string(CONF_FS_CHARSET, nullptr);
@@ -62,4 +57,5 @@ ConfigureFS()
if (charset != nullptr)
SetFSCharset(charset);
+#endif
}
diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx
index 9d7035706..d4f1709f5 100644
--- a/src/fs/Config.hxx
+++ b/src/fs/Config.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx
index c9d2c04b8..f77c0629f 100644
--- a/src/fs/DirectoryReader.hxx
+++ b/src/fs/DirectoryReader.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
#define MPD_FS_DIRECTORY_READER_HXX
#include "check.h"
-#include "AllocatedPath.hxx"
+#include "Path.hxx"
#include <dirent.h>
@@ -78,9 +78,9 @@ public:
/**
* Extracts directory entry that was previously read by #ReadEntry.
*/
- AllocatedPath GetEntry() const {
+ Path GetEntry() const {
assert(HasEntry());
- return AllocatedPath::FromFS(ent->d_name);
+ return Path::FromFS(ent->d_name);
}
};
diff --git a/src/fs/Domain.cxx b/src/fs/Domain.cxx
index 0877bca4c..4f3129219 100644
--- a/src/fs/Domain.cxx
+++ b/src/fs/Domain.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/Domain.hxx b/src/fs/Domain.hxx
index b303570fc..1fd17b37f 100644
--- a/src/fs/Domain.hxx
+++ b/src/fs/Domain.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx
index 4cd9f33b2..4e7c87415 100644
--- a/src/fs/FileSystem.cxx
+++ b/src/fs/FileSystem.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx
index cb2f82d22..4dbb064cb 100644
--- a/src/fs/FileSystem.hxx
+++ b/src/fs/FileSystem.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,7 +28,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#include <assert.h>
#include <stdio.h>
class AllocatedPath;
@@ -37,39 +36,39 @@ namespace FOpenMode {
/**
* Open mode for reading text files.
*/
- constexpr PathTraits::const_pointer ReadText = "r";
+ constexpr PathTraitsFS::const_pointer ReadText = "r";
/**
* Open mode for reading binary files.
*/
- constexpr PathTraits::const_pointer ReadBinary = "rb";
+ constexpr PathTraitsFS::const_pointer ReadBinary = "rb";
/**
* Open mode for writing text files.
*/
- constexpr PathTraits::const_pointer WriteText = "w";
+ constexpr PathTraitsFS::const_pointer WriteText = "w";
/**
* Open mode for writing binary files.
*/
- constexpr PathTraits::const_pointer WriteBinary = "wb";
+ constexpr PathTraitsFS::const_pointer WriteBinary = "wb";
/**
* Open mode for appending text files.
*/
- constexpr PathTraits::const_pointer AppendText = "a";
+ constexpr PathTraitsFS::const_pointer AppendText = "a";
/**
* Open mode for appending binary files.
*/
- constexpr PathTraits::const_pointer AppendBinary = "ab";
+ constexpr PathTraitsFS::const_pointer AppendBinary = "ab";
}
/**
* Wrapper for fopen() that uses #Path names.
*/
static inline FILE *
-FOpen(Path file, PathTraits::const_pointer mode)
+FOpen(Path file, PathTraitsFS::const_pointer mode)
{
return fopen(file.c_str(), mode);
}
@@ -132,20 +131,28 @@ MakeFifo(Path path, mode_t mode)
return mkfifo(path.c_str(), mode) == 0;
}
-#endif
-
/**
* Wrapper for access() that uses #Path names.
*/
static inline bool
CheckAccess(Path path, int mode)
{
+ return access(path.c_str(), mode) == 0;
+}
+
+#endif
+
+/**
+ * Checks is specified path exists and accessible.
+ */
+static inline bool
+CheckAccess(Path path)
+{
#ifdef WIN32
- (void)path;
- (void)mode;
- return true;
+ struct stat buf;
+ return StatFile(path, buf);
#else
- return access(path.c_str(), mode) == 0;
+ return CheckAccess(path, F_OK);
#endif
}
diff --git a/src/fs/Limits.hxx b/src/fs/Limits.hxx
index 480b08851..432897a69 100644
--- a/src/fs/Limits.hxx
+++ b/src/fs/Limits.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx
index 0ff0591fb..8288a4fec 100644
--- a/src/fs/Path.cxx
+++ b/src/fs/Path.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,25 +26,3 @@ Path::ToUTF8() const
{
return ::PathToUTF8(c_str());
}
-
-const char *
-Path::RelativeFS(const char *other_fs) const
-{
- const size_t l = length();
- if (memcmp(data(), other_fs, l) != 0)
- return nullptr;
-
- other_fs += l;
- if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
- /* mismatch */
- return nullptr;
-
- /* skip remaining path separators */
- do {
- ++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
- }
-
- return other_fs;
-}
diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx
index 6ea954577..9e0fa5aeb 100644
--- a/src/fs/Path.hxx
+++ b/src/fs/Path.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,8 +29,6 @@
#include <assert.h>
#include <string.h>
-class Error;
-
/**
* A path name in the native file system character set.
*
@@ -38,9 +36,9 @@ class Error;
* instance lives, the string must not be invalidated.
*/
class Path {
- typedef PathTraits::value_type value_type;
- typedef PathTraits::pointer pointer;
- typedef PathTraits::const_pointer const_pointer;
+ typedef PathTraitsFS::value_type value_type;
+ typedef PathTraitsFS::pointer pointer;
+ typedef PathTraitsFS::const_pointer const_pointer;
const char *value;
@@ -137,11 +135,13 @@ public:
* nullptr on mismatch.
*/
gcc_pure
- const char *RelativeFS(const char *other_fs) const;
+ const char *RelativeFS(const char *other_fs) const {
+ return PathTraitsFS::Relative(value, other_fs);
+ }
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx
new file mode 100644
index 000000000..7a836f906
--- /dev/null
+++ b/src/fs/StandardDirectory.cxx
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+// Use X Desktop guidelines where applicable
+#if !defined(__APPLE__) && !defined(WIN32) && !defined(ANDROID)
+#define USE_XDG
+#endif
+
+#include "StandardDirectory.hxx"
+#include "FileSystem.hxx"
+
+#include <array>
+
+#ifdef WIN32
+#include <windows.h>
+#include <shlobj.h>
+#else
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#endif
+
+#ifdef USE_XDG
+#include "util/Error.hxx"
+#include "util/StringUtil.hxx"
+#include "io/TextFile.hxx"
+#include <string.h>
+#include <utility>
+#endif
+
+#ifdef ANDROID
+#include "java/Global.hxx"
+#include "android/Environment.hxx"
+#include "android/Context.hxx"
+#include "Main.hxx"
+#endif
+
+#ifndef WIN32
+class PasswdEntry
+{
+#if defined(HAVE_GETPWNAM_R) || defined(HAVE_GETPWUID_R)
+ std::array<char, 16 * 1024> buf;
+ passwd pw;
+#endif
+
+ passwd *result;
+public:
+ PasswdEntry() : result(nullptr) { }
+
+ bool ReadByName(const char *name) {
+#ifdef HAVE_GETPWNAM_R
+ getpwnam_r(name, &pw, buf.data(), buf.size(), &result);
+#else
+ result = getpwnam(name);
+#endif
+ return result != nullptr;
+ }
+
+ bool ReadByUid(uid_t uid) {
+#ifdef HAVE_GETPWUID_R
+ getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
+#else
+ result = getpwuid(uid);
+#endif
+ return result != nullptr;
+ }
+
+ const passwd *operator->() {
+ assert(result != nullptr);
+ return result;
+ }
+};
+#endif
+
+static inline bool IsValidPathString(PathTraitsFS::const_pointer path)
+{
+ return path != nullptr && *path != '\0';
+}
+
+static inline bool IsValidDir(PathTraitsFS::const_pointer dir)
+{
+ return PathTraitsFS::IsAbsolute(dir) &&
+ DirectoryExists(Path::FromFS(dir));
+}
+
+static inline AllocatedPath SafePathFromFS(PathTraitsFS::const_pointer dir)
+{
+ if (IsValidPathString(dir) && IsValidDir(dir))
+ return AllocatedPath::FromFS(dir);
+ return AllocatedPath::Null();
+}
+
+#ifdef WIN32
+static AllocatedPath GetStandardDir(int folder_id)
+{
+ std::array<char, MAX_PATH> dir;
+ auto ret = SHGetFolderPath(nullptr, folder_id | CSIDL_FLAG_DONT_VERIFY,
+ nullptr, SHGFP_TYPE_CURRENT, dir.data());
+ if (FAILED(ret))
+ return AllocatedPath::Null();
+ return SafePathFromFS(dir.data());
+}
+#endif
+
+#ifdef USE_XDG
+
+static const char home_prefix[] = "$HOME/";
+
+static bool
+ParseConfigLine(char *line, const char *dir_name, AllocatedPath &result_dir)
+{
+ // strip leading white space
+ line = StripLeft(line);
+
+ // check for end-of-line or comment
+ if (*line == '\0' || *line == '#')
+ return false;
+
+ // check if current setting is for requested dir
+ if (!StringStartsWith(line, dir_name))
+ return false;
+ line += strlen(dir_name);
+
+ // strip equals sign and spaces around it
+ line = StripLeft(line);
+ if (*line != '=')
+ return false;
+ ++line;
+ line = StripLeft(line);
+
+ // check if path is quoted
+ bool quoted = false;
+ if (*line == '"') {
+ ++line;
+ quoted = true;
+ }
+
+ // check if path is relative to $HOME
+ bool home_relative = false;
+ if (StringStartsWith(line, home_prefix)) {
+ line += strlen(home_prefix);
+ home_relative = true;
+ }
+
+
+ char *line_end;
+ // find end of the string
+ if (quoted) {
+ line_end = strrchr(line, '"');
+ if (line_end == nullptr)
+ return true;
+ } else {
+ line_end = StripRight(line, line + strlen(line));
+ }
+
+ // check for empty result
+ if (line == line_end)
+ return true;
+
+ *line_end = 0;
+
+ // build the result path
+ const char *path = line;
+
+ auto result = AllocatedPath::Null();
+ if (home_relative) {
+ auto home = GetHomeDir();
+ if (home.IsNull())
+ return true;
+ result = AllocatedPath::Build(home, path);
+ } else {
+ result = AllocatedPath::FromFS(path);
+ }
+
+ if (IsValidDir(result.c_str())) {
+ result_dir = std::move(result);
+ return true;
+ }
+ return true;
+}
+
+static AllocatedPath GetUserDir(const char *name)
+{
+ auto result = AllocatedPath::Null();
+ auto config_dir = GetUserConfigDir();
+ if (config_dir.IsNull())
+ return result;
+ auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs");
+ TextFile input(dirs_file, IgnoreError());
+ if (input.HasFailed())
+ return result;
+ char *line;
+ while ((line = input.ReadLine()) != nullptr)
+ if (ParseConfigLine(line, name, result))
+ return result;
+ return result;
+}
+
+#endif
+
+AllocatedPath GetUserConfigDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_LOCAL_APPDATA);
+#elif defined(USE_XDG)
+ // Check for $XDG_CONFIG_HOME
+ auto config_home = getenv("XDG_CONFIG_HOME");
+ if (IsValidPathString(config_home) && IsValidDir(config_home))
+ return AllocatedPath::FromFS(config_home);
+
+ // Check for $HOME/.config
+ auto home = GetHomeDir();
+ if (!home.IsNull()) {
+ AllocatedPath fallback = AllocatedPath::Build(home, ".config");
+ if (IsValidDir(fallback.c_str()))
+ return fallback;
+ }
+
+ return AllocatedPath::Null();
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath GetUserMusicDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_MYMUSIC);
+#elif defined(USE_XDG)
+ return GetUserDir("XDG_MUSIC_DIR");
+#elif defined(ANDROID)
+ return Environment::getExternalStoragePublicDirectory("Music");
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath GetUserCacheDir()
+{
+#ifdef USE_XDG
+ // Check for $XDG_CACHE_HOME
+ auto cache_home = getenv("XDG_CACHE_HOME");
+ if (IsValidPathString(cache_home) && IsValidDir(cache_home))
+ return AllocatedPath::FromFS(cache_home);
+
+ // Check for $HOME/.cache
+ auto home = GetHomeDir();
+ if (!home.IsNull()) {
+ AllocatedPath fallback = AllocatedPath::Build(home, ".cache");
+ if (IsValidDir(fallback.c_str()))
+ return fallback;
+ }
+
+ return AllocatedPath::Null();
+#elif defined(ANDROID)
+ return context->GetCacheDir(Java::GetEnv());
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+#ifdef WIN32
+
+AllocatedPath GetSystemConfigDir()
+{
+ return GetStandardDir(CSIDL_COMMON_APPDATA);
+}
+
+AllocatedPath GetAppBaseDir()
+{
+ std::array<char, MAX_PATH> app;
+ auto ret = GetModuleFileName(nullptr, app.data(), app.size());
+
+ // Check for error
+ if (ret == 0)
+ return AllocatedPath::Null();
+
+ // Check for truncation
+ if (ret == app.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ return AllocatedPath::Null();
+
+ auto app_path = AllocatedPath::FromFS(app.data());
+ return app_path.GetDirectoryName().GetDirectoryName();
+}
+
+#else
+
+AllocatedPath GetHomeDir()
+{
+ auto home = getenv("HOME");
+ if (IsValidPathString(home) && IsValidDir(home))
+ return AllocatedPath::FromFS(home);
+ PasswdEntry pw;
+ if (pw.ReadByUid(getuid()))
+ return SafePathFromFS(pw->pw_dir);
+ return AllocatedPath::Null();
+}
+
+AllocatedPath GetHomeDir(const char *user_name)
+{
+ assert(user_name != nullptr);
+ PasswdEntry pw;
+ if (pw.ReadByName(user_name))
+ return SafePathFromFS(pw->pw_dir);
+ return AllocatedPath::Null();
+}
+
+#endif
diff --git a/src/fs/StandardDirectory.hxx b/src/fs/StandardDirectory.hxx
new file mode 100644
index 000000000..e3fba375a
--- /dev/null
+++ b/src/fs/StandardDirectory.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FS_STANDARD_DIRECTORY_HXX
+#define MPD_FS_STANDARD_DIRECTORY_HXX
+
+#include "check.h"
+#include "AllocatedPath.hxx"
+
+/**
+ * Obtains configuration directory for the current user.
+ */
+AllocatedPath GetUserConfigDir();
+
+/**
+ * Obtains music directory for the current user.
+ */
+AllocatedPath GetUserMusicDir();
+
+/**
+ * Obtains cache directory for the current user.
+ */
+gcc_pure
+AllocatedPath
+GetUserCacheDir();
+
+#ifdef WIN32
+
+/**
+ * Obtains system configuration directory.
+ */
+AllocatedPath GetSystemConfigDir();
+
+/**
+ * Obtains application application base directory.
+ * Application base directory is a directory that contains 'bin' folder
+ * for current executable.
+ */
+AllocatedPath GetAppBaseDir();
+
+#else
+
+/**
+ * Obtains home directory for the current user.
+ */
+AllocatedPath GetHomeDir();
+
+/**
+ * Obtains home directory for the specified user.
+ */
+AllocatedPath GetHomeDir(const char *user_name);
+
+#endif
+
+#endif
diff --git a/src/fs/Traits.cxx b/src/fs/Traits.cxx
index 47cb5aee3..166b31f4e 100644
--- a/src/fs/Traits.cxx
+++ b/src/fs/Traits.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,30 +22,142 @@
#include <string.h>
-const char *
-PathTraits::GetBaseUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+BuildPathImpl(typename Traits::const_pointer a, size_t a_size,
+ typename Traits::const_pointer b, size_t b_size)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+ if (a_size == 0)
+ return typename Traits::string(b, b_size);
+ if (b_size == 0)
+ return typename Traits::string(a, a_size);
+
+ typename Traits::string result(a, a_size);
+
+ if (!Traits::IsSeparator(a[a_size - 1]))
+ result.push_back(Traits::SEPARATOR);
+
+ if (Traits::IsSeparator(b[0]))
+ result.append(b + 1, b_size - 1);
+ else
+ result.append(b, b_size);
+
+ return result;
+}
+
+template<typename Traits>
+typename Traits::const_pointer
+GetBasePathImpl(typename Traits::const_pointer p)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? slash + 1
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ return sep != nullptr
+ ? sep + 1
: p;
}
-std::string
-PathTraits::GetParentUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+GetParentPathImpl(typename Traits::const_pointer p)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? std::string(p, slash)
- : std::string(".");
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ if (sep == nullptr)
+ return typename Traits::string(".");
+ if (sep == p)
+ return typename Traits::string(p, p + 1);
+#ifdef WIN32
+ if (Traits::IsDrive(p) && sep == p + 2)
+ return typename Traits::string(p, p + 3);
+#endif
+ return typename Traits::string(p, sep);
+}
+
+template<typename Traits>
+typename Traits::const_pointer
+RelativePathImpl(typename Traits::const_pointer base,
+ typename Traits::const_pointer other)
+{
+ assert(base != nullptr);
+ assert(other != nullptr);
+
+ const auto base_length = Traits::GetLength(base);
+ if (memcmp(base, other, base_length * sizeof(*base)) != 0)
+ /* mismatch */
+ return nullptr;
+
+ other += base_length;
+ if (other != 0) {
+ if (!Traits::IsSeparator(*other))
+ /* mismatch */
+ return nullptr;
+
+ /* skip remaining path separators */
+ do {
+ ++other;
+ } while (Traits::IsSeparator(*other));
+ }
+
+ return other;
+}
+
+PathTraitsFS::string
+PathTraitsFS::Build(PathTraitsFS::const_pointer a, size_t a_size,
+ PathTraitsFS::const_pointer b, size_t b_size)
+{
+ return BuildPathImpl<PathTraitsFS>(a, a_size, b, b_size);
+}
+
+PathTraitsFS::const_pointer
+PathTraitsFS::GetBase(PathTraitsFS::const_pointer p)
+{
+ return GetBasePathImpl<PathTraitsFS>(p);
+}
+
+PathTraitsFS::string
+PathTraitsFS::GetParent(PathTraitsFS::const_pointer p)
+{
+ return GetParentPathImpl<PathTraitsFS>(p);
+}
+
+PathTraitsFS::const_pointer
+PathTraitsFS::Relative(const_pointer base, const_pointer other)
+{
+ return RelativePathImpl<PathTraitsFS>(base, other);
+}
+
+PathTraitsUTF8::string
+PathTraitsUTF8::Build(PathTraitsUTF8::const_pointer a, size_t a_size,
+ PathTraitsUTF8::const_pointer b, size_t b_size)
+{
+ return BuildPathImpl<PathTraitsUTF8>(a, a_size, b, b_size);
+}
+
+PathTraitsUTF8::const_pointer
+PathTraitsUTF8::GetBase(PathTraitsUTF8::const_pointer p)
+{
+ return GetBasePathImpl<PathTraitsUTF8>(p);
+}
+
+PathTraitsUTF8::string
+PathTraitsUTF8::GetParent(PathTraitsUTF8::const_pointer p)
+{
+ return GetParentPathImpl<PathTraitsUTF8>(p);
+}
+
+PathTraitsUTF8::const_pointer
+PathTraitsUTF8::Relative(const_pointer base, const_pointer other)
+{
+ return RelativePathImpl<PathTraitsUTF8>(base, other);
}
diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx
index 244ab8b5c..77317e1ee 100644
--- a/src/fs/Traits.hxx
+++ b/src/fs/Traits.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,67 +24,153 @@
#include "Compiler.h"
#ifdef WIN32
-#include <glib.h>
+#include "util/CharUtil.hxx"
#endif
#include <string>
+#include <string.h>
#include <assert.h>
-class Error;
-
/**
- * This class describes the nature of a filesystem path.
+ * This class describes the nature of a native filesystem path.
*/
-struct PathTraits {
+struct PathTraitsFS {
+ typedef std::string string;
typedef char value_type;
- typedef char *pointer;
- typedef const char *const_pointer;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
#ifdef WIN32
- static constexpr value_type SEPARATOR_FS = '\\';
- static constexpr char SEPARATOR_UTF8 = '/';
+ static constexpr value_type SEPARATOR = '\\';
#else
- static constexpr value_type SEPARATOR_FS = '/';
- static constexpr char SEPARATOR_UTF8 = '/';
+ static constexpr value_type SEPARATOR = '/';
#endif
- static constexpr bool IsSeparatorFS(value_type ch) {
+ static constexpr bool IsSeparator(value_type ch) {
return
#ifdef WIN32
ch == '/' ||
#endif
- ch == SEPARATOR_FS;
+ ch == SEPARATOR;
}
- static constexpr bool IsSeparatorUTF8(char ch) {
- return
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
+ assert(p != nullptr);
#ifdef WIN32
- ch == '/' ||
+ const_pointer pos = p + GetLength(p);
+ while (p != pos && !IsSeparator(*pos))
+ --pos;
+ return IsSeparator(*pos) ? pos : nullptr;
+#else
+ return strrchr(p, SEPARATOR);
#endif
- ch == SEPARATOR_UTF8;
}
- gcc_pure
- static bool IsAbsoluteFS(const_pointer p) {
- assert(p != nullptr);
+#ifdef WIN32
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
+#endif
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+ assert(p != nullptr);
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorFS(*p);
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
#endif
+ return IsSeparator(*p);
}
- gcc_pure
- static bool IsAbsoluteUTF8(const char *p) {
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
+ }
+
+ /**
+ * Determine the "base" file name of the given native path.
+ * The return value points inside the given string.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer GetBase(const_pointer p);
+
+ /**
+ * Determine the "parent" file name of the given native path.
+ * As a special case, returns the string "." if there is no
+ * separator in the given input string.
+ */
+ gcc_pure gcc_nonnull_all
+ static string GetParent(const_pointer p);
+
+ /**
+ * Determine the relative part of the given path to this
+ * object, not including the directory separator. Returns an
+ * empty string if the given path equals this object or
+ * nullptr on mismatch.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer Relative(const_pointer base, const_pointer other);
+
+ /**
+ * Constructs the path from the given components.
+ * If either of the components is empty string,
+ * remaining component is returned unchanged.
+ * If both components are empty strings, empty string is returned.
+ */
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size);
+
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, const_pointer b) {
+ return Build(a, GetLength(a), b, GetLength(b));
+ }
+};
+
+/**
+ * This class describes the nature of a MPD internal filesystem path.
+ */
+struct PathTraitsUTF8 {
+ typedef std::string string;
+ typedef char value_type;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
+
+ static constexpr value_type SEPARATOR = '/';
+
+ static constexpr bool IsSeparator(value_type ch) {
+ return ch == SEPARATOR;
+ }
+
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
assert(p != nullptr);
+ return strrchr(p, SEPARATOR);
+ }
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorUTF8(*p);
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
#endif
+
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+ assert(p != nullptr);
+#ifdef WIN32
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
+#endif
+ return IsSeparator(*p);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
}
/**
@@ -92,7 +178,7 @@ struct PathTraits {
* The return value points inside the given string.
*/
gcc_pure gcc_nonnull_all
- static const char *GetBaseUTF8(const char *p);
+ static const_pointer GetBase(const_pointer p);
/**
* Determine the "parent" file name of the given UTF-8 path.
@@ -100,7 +186,31 @@ struct PathTraits {
* separator in the given input string.
*/
gcc_pure gcc_nonnull_all
- static std::string GetParentUTF8(const char *p);
+ static string GetParent(const_pointer p);
+
+ /**
+ * Determine the relative part of the given path to this
+ * object, not including the directory separator. Returns an
+ * empty string if the given path equals this object or
+ * nullptr on mismatch.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer Relative(const_pointer base, const_pointer other);
+
+ /**
+ * Constructs the path from the given components.
+ * If either of the components is empty string,
+ * remaining component is returned unchanged.
+ * If both components are empty strings, empty string is returned.
+ */
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size);
+
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, const_pointer b) {
+ return Build(a, GetLength(a), b, GetLength(b));
+ }
};
#endif
diff --git a/src/fs/io/AutoGunzipReader.cxx b/src/fs/io/AutoGunzipReader.cxx
new file mode 100644
index 000000000..2552f7b99
--- /dev/null
+++ b/src/fs/io/AutoGunzipReader.cxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AutoGunzipReader.hxx"
+#include "GunzipReader.hxx"
+#include "util/Error.hxx"
+
+AutoGunzipReader::~AutoGunzipReader()
+{
+ delete gunzip;
+}
+
+gcc_pure
+static bool
+IsGzip(const uint8_t data[4])
+{
+ return data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08 &&
+ (data[3] & 0xe0) == 0;
+}
+
+inline bool
+AutoGunzipReader::Detect(Error &error)
+{
+ const uint8_t *data = (const uint8_t *)peek.Peek(4, error);
+ if (data == nullptr) {
+ if (error.IsDefined())
+ return false;
+
+ next = &peek;
+ return true;
+ }
+
+ if (IsGzip(data)) {
+ gunzip = new GunzipReader(peek, error);
+ if (!gunzip->IsDefined())
+ return false;
+
+
+ next = gunzip;
+ } else
+ next = &peek;
+
+ return true;
+}
+
+size_t
+AutoGunzipReader::Read(void *data, size_t size, Error &error)
+{
+ if (next == nullptr && !Detect(error))
+ return false;
+
+ return next->Read(data, size, error);
+}
diff --git a/src/fs/io/AutoGunzipReader.hxx b/src/fs/io/AutoGunzipReader.hxx
new file mode 100644
index 000000000..9f031e0f5
--- /dev/null
+++ b/src/fs/io/AutoGunzipReader.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AUTO_GUNZIP_READER_HXX
+#define MPD_AUTO_GUNZIP_READER_HXX
+
+#include "check.h"
+#include "PeekReader.hxx"
+#include "Compiler.h"
+
+#include <stdint.h>
+
+class GunzipReader;
+
+/**
+ * A filter that detects gzip compression and optionally inserts a
+ * #GunzipReader.
+ */
+class AutoGunzipReader final : public Reader {
+ Reader *next;
+ PeekReader peek;
+ GunzipReader *gunzip;
+
+public:
+ AutoGunzipReader(Reader &_next)
+ :next(nullptr), peek(_next), gunzip(nullptr) {}
+ ~AutoGunzipReader();
+
+ /* virtual methods from class Reader */
+ virtual size_t Read(void *data, size_t size, Error &error) override;
+
+private:
+ bool Detect(Error &error);
+};
+
+#endif
diff --git a/src/fs/io/BufferedOutputStream.cxx b/src/fs/io/BufferedOutputStream.cxx
new file mode 100644
index 000000000..088a3e279
--- /dev/null
+++ b/src/fs/io/BufferedOutputStream.cxx
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "BufferedOutputStream.hxx"
+#include "OutputStream.hxx"
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+bool
+BufferedOutputStream::AppendToBuffer(const void *data, size_t size)
+{
+ auto r = buffer.Write();
+ if (r.size < size)
+ return false;
+
+ memcpy(r.data, data, size);
+ buffer.Append(size);
+ return true;
+}
+
+bool
+BufferedOutputStream::Write(const void *data, size_t size)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ if (AppendToBuffer(data, size))
+ return true;
+
+ if (!Flush())
+ return false;
+
+ if (AppendToBuffer(data, size))
+ return true;
+
+ return os.Write(data, size, last_error);
+}
+
+bool
+BufferedOutputStream::Write(const char *p)
+{
+ return Write(p, strlen(p));
+}
+
+bool
+BufferedOutputStream::Format(const char *fmt, ...)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ auto r = buffer.Write();
+ if (r.IsEmpty()) {
+ if (!Flush())
+ return false;
+
+ r = buffer.Write();
+ }
+
+ /* format into the buffer */
+ va_list ap;
+ va_start(ap, fmt);
+ size_t size = vsnprintf(r.data, r.size, fmt, ap);
+ va_end(ap);
+
+ if (gcc_unlikely(size >= r.size)) {
+ /* buffer was not large enough; flush it and try
+ again */
+
+ if (!Flush())
+ return false;
+
+ r = buffer.Write();
+
+ if (gcc_unlikely(size >= r.size)) {
+ /* still not enough space: grow the buffer and
+ try again */
+ r.size = size + 1;
+ r.data = buffer.Write(r.size);
+ }
+
+ /* format into the new buffer */
+ va_start(ap, fmt);
+ size = vsnprintf(r.data, r.size, fmt, ap);
+ va_end(ap);
+
+ /* this time, it must fit */
+ assert(size < r.size);
+ }
+
+ buffer.Append(size);
+ return true;
+}
+
+bool
+BufferedOutputStream::Flush()
+{
+ if (!Check())
+ return false;
+
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ return true;
+
+ bool success = os.Write(r.data, r.size, last_error);
+ if (gcc_likely(success))
+ buffer.Consume(r.size);
+ return success;
+}
+
+bool
+BufferedOutputStream::Flush(Error &error)
+{
+ if (!Check(error))
+ return false;
+
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ return true;
+
+ bool success = os.Write(r.data, r.size, error);
+ if (gcc_likely(success))
+ buffer.Consume(r.size);
+ return success;
+}
diff --git a/src/fs/io/BufferedOutputStream.hxx b/src/fs/io/BufferedOutputStream.hxx
new file mode 100644
index 000000000..f2de758a2
--- /dev/null
+++ b/src/fs/io/BufferedOutputStream.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BUFFERED_OUTPUT_STREAM_HXX
+#define MPD_BUFFERED_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <stddef.h>
+
+class OutputStream;
+class Error;
+
+class BufferedOutputStream {
+ OutputStream &os;
+
+ DynamicFifoBuffer<char> buffer;
+
+ Error last_error;
+
+public:
+ BufferedOutputStream(OutputStream &_os)
+ :os(_os), buffer(32768) {}
+
+ bool Write(const void *data, size_t size);
+ bool Write(const char *p);
+
+ gcc_printf(2,3)
+ bool Format(const char *fmt, ...);
+
+ gcc_pure
+ bool Check() const {
+ return !last_error.IsDefined();
+ }
+
+ bool Check(Error &error) const {
+ if (last_error.IsDefined()) {
+ error.Set(last_error);
+ return false;
+ } else
+ return true;
+ }
+
+ bool Flush();
+
+ bool Flush(Error &error);
+
+private:
+ bool AppendToBuffer(const void *data, size_t size);
+};
+
+#endif
diff --git a/src/fs/io/BufferedReader.cxx b/src/fs/io/BufferedReader.cxx
new file mode 100644
index 000000000..ba2f17dcf
--- /dev/null
+++ b/src/fs/io/BufferedReader.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "BufferedReader.hxx"
+#include "Reader.hxx"
+#include "util/TextFile.hxx"
+
+bool
+BufferedReader::Fill(bool need_more)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ if (eof)
+ return !need_more;
+
+ auto w = buffer.Write();
+ if (w.IsEmpty()) {
+ if (buffer.GetCapacity() >= MAX_SIZE)
+ return !need_more;
+
+ buffer.Grow(buffer.GetCapacity() * 2);
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ }
+
+ size_t nbytes = reader.Read(w.data, w.size, last_error);
+ if (nbytes == 0) {
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ eof = true;
+ return !need_more;
+ }
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+char *
+BufferedReader::ReadLine()
+{
+ do {
+ char *line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+ } while (Fill(true));
+
+ if (last_error.IsDefined() || !eof || buffer.IsEmpty())
+ return nullptr;
+
+ auto w = buffer.Write();
+ if (w.IsEmpty()) {
+ buffer.Grow(buffer.GetCapacity() + 1);
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ }
+
+ /* terminate the last line */
+ w[0] = 0;
+
+ char *line = buffer.Read().data;
+ buffer.Clear();
+ return line;
+}
diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx
new file mode 100644
index 000000000..61cc8df83
--- /dev/null
+++ b/src/fs/io/BufferedReader.hxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BUFFERED_READER_HXX
+#define MPD_BUFFERED_READER_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <stddef.h>
+
+class Reader;
+class Error;
+
+class BufferedReader {
+ static constexpr size_t MAX_SIZE = 512 * 1024;
+
+ Reader &reader;
+
+ DynamicFifoBuffer<char> buffer;
+
+ Error last_error;
+
+ bool eof;
+
+public:
+ BufferedReader(Reader &_reader)
+ :reader(_reader), buffer(4096), eof(false) {}
+
+ gcc_pure
+ bool Check() const {
+ return !last_error.IsDefined();
+ }
+
+ bool Check(Error &error) const {
+ if (last_error.IsDefined()) {
+ error.Set(last_error);
+ return false;
+ } else
+ return true;
+ }
+
+ bool Fill(bool need_more);
+
+ gcc_pure
+ WritableBuffer<void> Read() const {
+ return buffer.Read().ToVoid();
+ }
+
+ void Consume(size_t n) {
+ buffer.Consume(n);
+ }
+
+ char *ReadLine();
+};
+
+#endif
diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx
new file mode 100644
index 000000000..dc4456d1f
--- /dev/null
+++ b/src/fs/io/FileOutputStream.cxx
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FileOutputStream.hxx"
+#include "fs/FileSystem.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#ifdef WIN32
+
+FileOutputStream::FileOutputStream(Path _path, Error &error)
+ :path(_path),
+ handle(CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr,
+ TRUNCATE_EXISTING,
+ FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
+ nullptr))
+{
+ if (handle == INVALID_HANDLE_VALUE)
+ error.FormatLastError("Failed to create %s", path.c_str());
+}
+
+bool
+FileOutputStream::Write(const void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ DWORD nbytes;
+ if (!WriteFile(handle, data, size, &nbytes, nullptr)) {
+ error.FormatLastError("Failed to write to %s", path.c_str());
+ return false;
+ }
+
+ if (size_t(nbytes) != size) {
+ error.FormatLastError(ERROR_DISK_FULL,
+ "Failed to write to %s", path.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool
+FileOutputStream::Commit(gcc_unused Error &error)
+{
+ assert(IsDefined());
+
+ CloseHandle(handle);
+ return true;
+}
+
+void
+FileOutputStream::Cancel()
+{
+ assert(IsDefined());
+
+ CloseHandle(handle);
+ RemoveFile(path);
+}
+
+#else
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+FileOutputStream::FileOutputStream(Path _path, Error &error)
+ :path(_path),
+ fd(open_cloexec(path.c_str(),
+ O_WRONLY|O_CREAT|O_TRUNC,
+ 0666))
+{
+ if (fd < 0)
+ error.FormatErrno("Failed to create %s", path.c_str());
+}
+
+bool
+FileOutputStream::Write(const void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ ssize_t nbytes = write(fd, data, size);
+ if (nbytes < 0) {
+ error.FormatErrno("Failed to write to %s", path.c_str());
+ return false;
+ } else if ((size_t)nbytes < size) {
+ error.FormatErrno(ENOSPC,
+ "Failed to write to %s", path.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool
+FileOutputStream::Commit(Error &error)
+{
+ assert(IsDefined());
+
+ bool success = close(fd) == 0;
+ fd = -1;
+ if (!success)
+ error.FormatErrno("Failed to commit %s", path.c_str());
+
+ return success;
+}
+
+void
+FileOutputStream::Cancel()
+{
+ assert(IsDefined());
+
+ close(fd);
+ fd = -1;
+
+ RemoveFile(path);
+}
+
+#endif
diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx
new file mode 100644
index 000000000..5b6309957
--- /dev/null
+++ b/src/fs/io/FileOutputStream.hxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FILE_OUTPUT_STREAM_HXX
+#define MPD_FILE_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "OutputStream.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+class Path;
+
+class FileOutputStream final : public OutputStream {
+ AllocatedPath path;
+
+#ifdef WIN32
+ HANDLE handle;
+#else
+ int fd;
+#endif
+
+public:
+ FileOutputStream(Path _path, Error &error);
+
+ ~FileOutputStream() {
+ if (IsDefined())
+ Cancel();
+ }
+
+
+ bool IsDefined() const {
+#ifdef WIN32
+ return handle != INVALID_HANDLE_VALUE;
+#else
+ return fd >= 0;
+#endif
+ }
+
+ bool Commit(Error &error);
+ void Cancel();
+
+ /* virtual methods from class OutputStream */
+ bool Write(const void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/FileReader.cxx b/src/fs/io/FileReader.cxx
new file mode 100644
index 000000000..d63cd8ab0
--- /dev/null
+++ b/src/fs/io/FileReader.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FileReader.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#ifdef WIN32
+
+FileReader::FileReader(Path _path, Error &error)
+ :path(_path),
+ handle(CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ nullptr))
+{
+ if (handle == INVALID_HANDLE_VALUE)
+ error.FormatLastError("Failed to open %s", path.c_str());
+}
+
+size_t
+FileReader::Read(void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ DWORD nbytes;
+ if (!ReadFile(handle, data, size, &nbytes, nullptr)) {
+ error.FormatLastError("Failed to read from %s", path.c_str());
+ nbytes = 0;
+ }
+
+ return nbytes;
+}
+
+void
+FileReader::Close()
+{
+ assert(IsDefined());
+
+ CloseHandle(handle);
+}
+
+#else
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+FileReader::FileReader(Path _path, Error &error)
+ :path(_path),
+ fd(open_cloexec(path.c_str(),
+ O_RDONLY,
+ 0))
+{
+ if (fd < 0)
+ error.FormatErrno("Failed to open %s", path.c_str());
+}
+
+size_t
+FileReader::Read(void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ ssize_t nbytes = read(fd, data, size);
+ if (nbytes < 0) {
+ error.FormatErrno("Failed to read from %s", path.c_str());
+ nbytes = 0;
+ }
+
+ return nbytes;
+}
+
+void
+FileReader::Close()
+{
+ assert(IsDefined());
+
+ close(fd);
+ fd = -1;
+}
+
+#endif
diff --git a/src/fs/io/FileReader.hxx b/src/fs/io/FileReader.hxx
new file mode 100644
index 000000000..9f459aee2
--- /dev/null
+++ b/src/fs/io/FileReader.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FILE_READER_HXX
+#define MPD_FILE_READER_HXX
+
+#include "check.h"
+#include "Reader.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+class Path;
+
+class FileReader final : public Reader {
+ AllocatedPath path;
+
+#ifdef WIN32
+ HANDLE handle;
+#else
+ int fd;
+#endif
+
+public:
+ FileReader(Path _path, Error &error);
+
+ ~FileReader() {
+ if (IsDefined())
+ Close();
+ }
+
+
+ bool IsDefined() const {
+#ifdef WIN32
+ return handle != INVALID_HANDLE_VALUE;
+#else
+ return fd >= 0;
+#endif
+ }
+
+ void Close();
+
+ /* virtual methods from class Reader */
+ size_t Read(void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/GunzipReader.cxx b/src/fs/io/GunzipReader.cxx
new file mode 100644
index 000000000..ad5e41784
--- /dev/null
+++ b/src/fs/io/GunzipReader.cxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "GunzipReader.hxx"
+#include "lib/zlib/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+GunzipReader::GunzipReader(Reader &_next, Error &error)
+ :next(_next), eof(false)
+{
+ z.next_in = nullptr;
+ z.avail_in = 0;
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ int result = inflateInit2(&z, 16 + MAX_WBITS);
+ if (result != Z_OK) {
+ z.opaque = this;
+ error.Set(zlib_domain, result, zError(result));
+ }
+}
+
+GunzipReader::~GunzipReader()
+{
+ if (IsDefined())
+ inflateEnd(&z);
+}
+
+inline bool
+GunzipReader::FillBuffer(Error &error)
+{
+ auto w = buffer.Write();
+ assert(!w.IsEmpty());
+
+ size_t nbytes = next.Read(w.data, w.size, error);
+ if (nbytes == 0)
+ return false;
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+size_t
+GunzipReader::Read(void *data, size_t size, Error &error)
+{
+ if (eof)
+ return 0;
+
+ z.next_out = (Bytef *)data;
+ z.avail_out = size;
+
+ while (true) {
+ int flush = Z_NO_FLUSH;
+
+ auto r = buffer.Read();
+ if (r.IsEmpty()) {
+ if (FillBuffer(error))
+ r = buffer.Read();
+ else if (error.IsDefined())
+ return 0;
+ else
+ flush = Z_FINISH;
+ }
+
+ z.next_in = r.data;
+ z.avail_in = r.size;
+
+ int result = inflate(&z, flush);
+ if (result == Z_STREAM_END) {
+ eof = true;
+ return size - z.avail_out;
+ } else if (result != Z_OK) {
+ error.Set(zlib_domain, result, zError(result));
+ return 0;
+ }
+
+ buffer.Consume(r.size - z.avail_in);
+
+ if (z.avail_out < size)
+ return size - z.avail_out;
+ }
+}
diff --git a/src/fs/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx
new file mode 100644
index 000000000..06c44bad6
--- /dev/null
+++ b/src/fs/io/GunzipReader.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_GUNZIP_READER_HXX
+#define MPD_GUNZIP_READER_HXX
+
+#include "check.h"
+#include "Reader.hxx"
+#include "util/StaticFifoBuffer.hxx"
+#include "Compiler.h"
+
+#include <zlib.h>
+
+class Error;
+class Domain;
+
+/**
+ * A filter that decompresses data using zlib.
+ */
+class GunzipReader final : public Reader {
+ Reader &next;
+
+ bool eof;
+
+ z_stream z;
+
+ StaticFifoBuffer<Bytef, 4096> buffer;
+
+public:
+ /**
+ * Construct the filter. Call IsDefined() to check whether
+ * the constructor has succeeded. If not, #error will hold
+ * information about the failure.
+ */
+ GunzipReader(Reader &_next, Error &error);
+ ~GunzipReader();
+
+ /**
+ * Check whether the constructor has succeeded.
+ */
+ bool IsDefined() const {
+ return z.opaque == nullptr;
+ }
+
+ /* virtual methods from class Reader */
+ virtual size_t Read(void *data, size_t size, Error &error) override;
+
+private:
+ bool FillBuffer(Error &error);
+};
+
+#endif
diff --git a/src/fs/io/GzipOutputStream.cxx b/src/fs/io/GzipOutputStream.cxx
new file mode 100644
index 000000000..27ae6b2ad
--- /dev/null
+++ b/src/fs/io/GzipOutputStream.cxx
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "GzipOutputStream.hxx"
+#include "lib/zlib/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+GzipOutputStream::GzipOutputStream(OutputStream &_next, Error &error)
+ :next(_next)
+{
+ z.next_in = nullptr;
+ z.avail_in = 0;
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ constexpr int windowBits = 15;
+ constexpr int gzip_encoding = 16;
+
+ int result = deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ windowBits | gzip_encoding,
+ 8, Z_DEFAULT_STRATEGY);
+ if (result != Z_OK) {
+ z.opaque = this;
+ error.Set(zlib_domain, result, zError(result));
+ }
+}
+
+GzipOutputStream::~GzipOutputStream()
+{
+ if (IsDefined())
+ deflateEnd(&z);
+}
+
+bool
+GzipOutputStream::Flush(Error &error)
+{
+ /* no more input */
+ z.next_in = nullptr;
+ z.avail_in = 0;
+
+ while (true) {
+ Bytef output[4096];
+ z.next_out = output;
+ z.avail_out = sizeof(output);
+
+ int result = deflate(&z, Z_FINISH);
+ if (z.next_out > output &&
+ !next.Write(output, z.next_out - output, error))
+ return false;
+
+ if (result == Z_STREAM_END)
+ return true;
+ else if (result != Z_OK) {
+ error.Set(zlib_domain, result, zError(result));
+ return false;
+ }
+ }
+}
+
+bool
+GzipOutputStream::Write(const void *_data, size_t size, Error &error)
+{
+ /* zlib's API requires non-const input pointer */
+ void *data = const_cast<void *>(_data);
+
+ z.next_in = reinterpret_cast<Bytef *>(data);
+ z.avail_in = size;
+
+ while (z.avail_in > 0) {
+ Bytef output[4096];
+ z.next_out = output;
+ z.avail_out = sizeof(output);
+
+ int result = deflate(&z, Z_NO_FLUSH);
+ if (result != Z_OK) {
+ error.Set(zlib_domain, result, zError(result));
+ return false;
+ }
+
+ if (z.next_out > output &&
+ !next.Write(output, z.next_out - output, error))
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/fs/io/GzipOutputStream.hxx b/src/fs/io/GzipOutputStream.hxx
new file mode 100644
index 000000000..27ee2dd24
--- /dev/null
+++ b/src/fs/io/GzipOutputStream.hxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_GZIP_OUTPUT_STREAM_HXX
+#define MPD_GZIP_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "OutputStream.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+#include <zlib.h>
+
+class Error;
+class Domain;
+
+/**
+ * A filter that compresses data written to it using zlib, forwarding
+ * compressed data in the "gzip" format.
+ *
+ * Don't forget to call Flush() before destructing this object.
+ */
+class GzipOutputStream final : public OutputStream {
+ OutputStream &next;
+
+ z_stream z;
+
+public:
+ /**
+ * Construct the filter. Call IsDefined() to check whether
+ * the constructor has succeeded. If not, #error will hold
+ * information about the failure.
+ */
+ GzipOutputStream(OutputStream &_next, Error &error);
+ ~GzipOutputStream();
+
+ /**
+ * Check whether the constructor has succeeded.
+ */
+ bool IsDefined() const {
+ return z.opaque == nullptr;
+ }
+
+ /**
+ * Finish the file and write all data remaining in zlib's
+ * output buffer.
+ */
+ bool Flush(Error &error);
+
+ /* virtual methods from class OutputStream */
+ bool Write(const void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/OutputStream.hxx b/src/fs/io/OutputStream.hxx
new file mode 100644
index 000000000..71311c71f
--- /dev/null
+++ b/src/fs/io/OutputStream.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_STREAM_HXX
+#define MPD_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <stddef.h>
+
+class Error;
+
+class OutputStream {
+public:
+ OutputStream() = default;
+ OutputStream(const OutputStream &) = delete;
+
+ virtual bool Write(const void *data, size_t size, Error &error) = 0;
+};
+
+#endif
diff --git a/src/fs/io/PeekReader.cxx b/src/fs/io/PeekReader.cxx
new file mode 100644
index 000000000..2e8042ab6
--- /dev/null
+++ b/src/fs/io/PeekReader.cxx
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PeekReader.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+
+const void *
+PeekReader::Peek(size_t size, Error &error)
+{
+ assert(size > 0);
+ assert(size < sizeof(buffer));
+ assert(buffer_size == 0);
+ assert(buffer_position == 0);
+
+ do {
+ size_t nbytes = next.Read(buffer + buffer_size,
+ size - buffer_size, error);
+ if (nbytes == 0)
+ return nullptr;
+
+ buffer_size += nbytes;
+ } while (buffer_size < size);
+
+ return buffer;
+}
+
+size_t
+PeekReader::Read(void *data, size_t size, Error &error)
+{
+ size_t buffer_remaining = buffer_size - buffer_position;
+ if (buffer_remaining > 0) {
+ size_t nbytes = std::min(buffer_remaining, size);
+ memcpy(data, buffer + buffer_position, nbytes);
+ buffer_position += nbytes;
+ return nbytes;
+ }
+
+ return next.Read(data, size, error);
+}
diff --git a/src/fs/io/PeekReader.hxx b/src/fs/io/PeekReader.hxx
new file mode 100644
index 000000000..32180b0a8
--- /dev/null
+++ b/src/fs/io/PeekReader.hxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PEEK_READER_HXX
+#define MPD_PEEK_READER_HXX
+
+#include "check.h"
+#include "Reader.hxx"
+#include "Compiler.h"
+
+#include <stdint.h>
+
+class AutoGunzipReader;
+
+/**
+ * A filter that allows the caller to peek the first few bytes without
+ * consuming them. The first call must be Peek(), and the following
+ * Read() will deliver the same bytes again.
+ */
+class PeekReader final : public Reader {
+ Reader &next;
+
+ size_t buffer_size, buffer_position;
+
+ uint8_t buffer[64];
+
+public:
+ PeekReader(Reader &_next)
+ :next(_next), buffer_size(0), buffer_position(0) {}
+
+ const void *Peek(size_t size, Error &error);
+
+ /* virtual methods from class Reader */
+ virtual size_t Read(void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/Reader.hxx b/src/fs/io/Reader.hxx
new file mode 100644
index 000000000..d41e92dd0
--- /dev/null
+++ b/src/fs/io/Reader.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_READER_HXX
+#define MPD_READER_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <stddef.h>
+
+class Error;
+
+/**
+ * An interface that can read bytes from a stream until the stream
+ * ends.
+ *
+ * This interface is simpler and less cumbersome to use than
+ * #InputStream.
+ */
+class Reader {
+public:
+ Reader() = default;
+ Reader(const Reader &) = delete;
+
+ /**
+ * Read data from the stream.
+ *
+ * @return the number of bytes read into the given buffer or 0
+ * on error/end-of-stream
+ */
+ gcc_nonnull_all
+ virtual size_t Read(void *data, size_t size, Error &error) = 0;
+};
+
+#endif
diff --git a/src/fs/io/StdioOutputStream.hxx b/src/fs/io/StdioOutputStream.hxx
new file mode 100644
index 000000000..c1c0a00bd
--- /dev/null
+++ b/src/fs/io/StdioOutputStream.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STDIO_OUTPUT_STREAM_HXX
+#define MPD_STDIO_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "OutputStream.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Compiler.h"
+
+#include <stdio.h>
+
+class StdioOutputStream final : public OutputStream {
+ FILE *const file;
+
+public:
+ StdioOutputStream(FILE *_file):file(_file) {}
+
+ /* virtual methods from class OutputStream */
+ bool Write(const void *data, size_t size,
+ gcc_unused Error &error) override {
+ fwrite(data, 1, size, file);
+
+ /* this class is debug-only and ignores errors */
+ return true;
+ }
+};
+
+#endif
diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx
new file mode 100644
index 000000000..28d6dabcb
--- /dev/null
+++ b/src/fs/io/TextFile.cxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TextFile.hxx"
+#include "FileReader.hxx"
+#include "AutoGunzipReader.hxx"
+#include "BufferedReader.hxx"
+#include "fs/Path.hxx"
+
+#include <assert.h>
+
+TextFile::TextFile(Path path_fs, Error &error)
+ :file_reader(new FileReader(path_fs, error)),
+#ifdef HAVE_ZLIB
+ gunzip_reader(file_reader->IsDefined()
+ ? new AutoGunzipReader(*file_reader)
+ : nullptr),
+#endif
+ buffered_reader(file_reader->IsDefined()
+ ? new BufferedReader(*
+#ifdef HAVE_ZLIB
+ gunzip_reader
+#else
+ file_reader
+#endif
+ )
+ : nullptr)
+{
+}
+
+TextFile::~TextFile()
+{
+ delete buffered_reader;
+#ifdef HAVE_ZLIB
+ delete gunzip_reader;
+#endif
+ delete file_reader;
+}
+
+char *
+TextFile::ReadLine()
+{
+ assert(buffered_reader != nullptr);
+
+ return buffered_reader->ReadLine();
+}
+
+bool
+TextFile::Check(Error &error) const
+{
+ assert(buffered_reader != nullptr);
+
+ return buffered_reader->Check(error);
+}
diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx
new file mode 100644
index 000000000..5577363e7
--- /dev/null
+++ b/src/fs/io/TextFile.hxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TEXT_FILE_HXX
+#define MPD_TEXT_FILE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <stddef.h>
+
+class Path;
+class Error;
+class FileReader;
+class AutoGunzipReader;
+class BufferedReader;
+
+class TextFile {
+ FileReader *const file_reader;
+
+#ifdef HAVE_ZLIB
+ AutoGunzipReader *const gunzip_reader;
+#endif
+
+ BufferedReader *const buffered_reader;
+
+public:
+ TextFile(Path path_fs, Error &error);
+
+ TextFile(const TextFile &other) = delete;
+
+ ~TextFile();
+
+ bool HasFailed() const {
+ return gcc_unlikely(buffered_reader == 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.
+ *
+ * Use Check() after nullptr has been returned to check
+ * whether an error occurred or end-of-file has been reached.
+ *
+ * @param file the source file, opened in text mode
+ * @return a pointer to the line, or nullptr on end-of-file or error
+ */
+ char *ReadLine();
+
+ /**
+ * Check whether a ReadLine() call has thrown an error.
+ */
+ bool Check(Error &error) const;
+};
+
+#endif
diff --git a/src/gerror.h b/src/gerror.h
deleted file mode 100644
index fe4c54da9..000000000
--- a/src/gerror.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_GERROR_H
-#define MPD_GERROR_H
-
-typedef struct _GError GError;
-
-#endif
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx
deleted file mode 100644
index 5288f2b3b..000000000
--- a/src/input/ArchiveInputPlugin.cxx
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ArchiveInputPlugin.hxx"
-#include "ArchiveDomain.hxx"
-#include "ArchiveLookup.hxx"
-#include "ArchiveList.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "fs/Traits.hxx"
-#include "Log.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 InputStream *
-input_archive_open(const char *pathname,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- const struct archive_plugin *arplug;
- InputStream *is;
-
- if (!PathTraits::IsAbsoluteFS(pathname))
- return nullptr;
-
- char *pname = g_strdup(pathname);
- // archive_lookup will modify pname when true is returned
- const char *archive, *filename, *suffix;
- if (!archive_lookup(pname, &archive, &filename, &suffix)) {
- FormatDebug(archive_domain,
- "not an archive, lookup %s failed", pname);
- g_free(pname);
- return nullptr;
- }
-
- //check which archive plugin to use (by ext)
- arplug = archive_plugin_from_suffix(suffix);
- if (!arplug) {
- FormatWarning(archive_domain,
- "can't handle archive %s", archive);
- g_free(pname);
- return nullptr;
- }
-
- auto file = archive_file_open(arplug, archive, error);
- if (file == nullptr) {
- g_free(pname);
- return nullptr;
- }
-
- //setup fileops
- is = file->OpenStream(filename, mutex, cond, error);
- g_free(pname);
- file->Close();
-
- return is;
-}
-
-const InputPlugin 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
deleted file mode 100644
index 9ac70b2fc..000000000
--- a/src/input/ArchiveInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INPUT_ARCHIVE_HXX
-#define MPD_INPUT_ARCHIVE_HXX
-
-extern const struct InputPlugin input_plugin_archive;
-
-#endif
diff --git a/src/input/AsyncInputStream.cxx b/src/input/AsyncInputStream.cxx
new file mode 100644
index 000000000..c8e3fcfd5
--- /dev/null
+++ b/src/input/AsyncInputStream.cxx
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AsyncInputStream.hxx"
+#include "Domain.hxx"
+#include "tag/Tag.hxx"
+#include "event/Call.hxx"
+#include "thread/Cond.hxx"
+#include "IOThread.hxx"
+#include "util/HugeAllocator.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+AsyncInputStream::AsyncInputStream(const char *_url,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer, size_t _buffer_size,
+ size_t _resume_at)
+ :InputStream(_url, _mutex, _cond), DeferredMonitor(io_thread_get()),
+ buffer((uint8_t *)_buffer, _buffer_size),
+ resume_at(_resume_at),
+ open(true),
+ paused(false),
+ seek_state(SeekState::NONE),
+ tag(nullptr) {}
+
+AsyncInputStream::~AsyncInputStream()
+{
+ delete tag;
+
+ buffer.Clear();
+ HugeFree(buffer.Write().data, buffer.GetCapacity());
+}
+
+void
+AsyncInputStream::SetTag(Tag *_tag)
+{
+ delete tag;
+ tag = _tag;
+}
+
+void
+AsyncInputStream::Pause()
+{
+ assert(io_thread_inside());
+
+ paused = true;
+}
+
+void
+AsyncInputStream::PostponeError(Error &&error)
+{
+ assert(io_thread_inside());
+
+ seek_state = SeekState::NONE;
+ postponed_error = std::move(error);
+ cond.broadcast();
+}
+
+inline void
+AsyncInputStream::Resume()
+{
+ assert(io_thread_inside());
+
+ if (paused) {
+ paused = false;
+ DoResume();
+ }
+}
+
+bool
+AsyncInputStream::Check(Error &error)
+{
+ bool success = !postponed_error.IsDefined();
+ if (!success) {
+ error = std::move(postponed_error);
+ postponed_error.Clear();
+ }
+
+ return success;
+}
+
+bool
+AsyncInputStream::IsEOF()
+{
+ return (KnownSize() && offset >= size) ||
+ (!open && buffer.IsEmpty());
+}
+
+bool
+AsyncInputStream::Seek(offset_type new_offset, Error &error)
+{
+ assert(IsReady());
+ assert(seek_state == SeekState::NONE);
+
+ if (new_offset == offset)
+ /* no-op */
+ return true;
+
+ if (!IsSeekable()) {
+ error.Set(input_domain, "Not seekable");
+ return false;
+ }
+
+ /* check if we can fast-forward the buffer */
+
+ while (new_offset > offset) {
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ break;
+
+ const size_t nbytes =
+ new_offset - offset < (offset_type)r.size
+ ? new_offset - offset
+ : r.size;
+
+ buffer.Consume(nbytes);
+ offset += nbytes;
+ }
+
+ if (new_offset == offset)
+ return true;
+
+ /* no: ask the implementation to seek */
+
+ seek_offset = new_offset;
+ seek_state = SeekState::SCHEDULED;
+
+ DeferredMonitor::Schedule();
+
+ while (seek_state != SeekState::NONE)
+ cond.wait(mutex);
+
+ if (!Check(error))
+ return false;
+
+ return true;
+}
+
+void
+AsyncInputStream::SeekDone()
+{
+ assert(io_thread_inside());
+ assert(IsSeekPending());
+
+ seek_state = SeekState::NONE;
+ cond.broadcast();
+}
+
+Tag *
+AsyncInputStream::ReadTag()
+{
+ Tag *result = tag;
+ tag = nullptr;
+ return result;
+}
+
+bool
+AsyncInputStream::IsAvailable()
+{
+ return postponed_error.IsDefined() ||
+ IsEOF() ||
+ !buffer.IsEmpty();
+}
+
+size_t
+AsyncInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ assert(!io_thread_inside());
+
+ /* wait for data */
+ CircularBuffer<uint8_t>::Range r;
+ while (true) {
+ if (!Check(error))
+ return 0;
+
+ r = buffer.Read();
+ if (!r.IsEmpty() || IsEOF())
+ break;
+
+ cond.wait(mutex);
+ }
+
+ const size_t nbytes = std::min(read_size, r.size);
+ memcpy(ptr, r.data, nbytes);
+ buffer.Consume(nbytes);
+
+ offset += (offset_type)nbytes;
+
+ if (paused && buffer.GetSize() < resume_at)
+ DeferredMonitor::Schedule();
+
+ return nbytes;
+}
+
+void
+AsyncInputStream::AppendToBuffer(const void *data, size_t append_size)
+{
+ auto w = buffer.Write();
+ assert(!w.IsEmpty());
+
+ size_t nbytes = std::min(w.size, append_size);
+ memcpy(w.data, data, nbytes);
+ buffer.Append(nbytes);
+
+ const size_t remaining = append_size - nbytes;
+ if (remaining > 0) {
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ assert(w.size >= remaining);
+
+ memcpy(w.data, (const uint8_t *)data + nbytes, remaining);
+ buffer.Append(remaining);
+ }
+
+ if (!IsReady())
+ SetReady();
+ else
+ cond.broadcast();
+}
+
+void
+AsyncInputStream::RunDeferred()
+{
+ const ScopeLock protect(mutex);
+
+ Resume();
+
+ if (seek_state == SeekState::SCHEDULED) {
+ seek_state = SeekState::PENDING;
+ buffer.Clear();
+ paused = false;
+ DoSeek(seek_offset);
+ }
+}
diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx
new file mode 100644
index 000000000..d1f0c3b9d
--- /dev/null
+++ b/src/input/AsyncInputStream.hxx
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ASYNC_INPUT_STREAM_HXX
+#define MPD_ASYNC_INPUT_STREAM_HXX
+
+#include "InputStream.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/Error.hxx"
+
+/**
+ * Helper class for moving asynchronous (non-blocking) InputStream
+ * implementations to the I/O thread. Data is being read into a ring
+ * buffer, and that buffer is then consumed by another thread using
+ * the regular #InputStream API.
+ */
+class AsyncInputStream : public InputStream, private DeferredMonitor {
+ enum class SeekState : uint8_t {
+ NONE, SCHEDULED, PENDING
+ };
+
+ CircularBuffer<uint8_t> buffer;
+ const size_t resume_at;
+
+ bool open;
+
+ /**
+ * 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;
+
+ SeekState seek_state;
+
+ /**
+ * The #Tag object ready to be requested via
+ * InputStream::ReadTag().
+ */
+ Tag *tag;
+
+ offset_type seek_offset;
+
+protected:
+ Error postponed_error;
+
+public:
+ AsyncInputStream(const char *_url,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer, size_t _buffer_size,
+ size_t _resume_at);
+
+ virtual ~AsyncInputStream();
+
+ /* virtual methods from InputStream */
+ bool Check(Error &error) final;
+ bool IsEOF() final;
+ bool Seek(offset_type new_offset, Error &error) final;
+ Tag *ReadTag() final;
+ bool IsAvailable() final;
+ size_t Read(void *ptr, size_t read_size, Error &error) final;
+
+protected:
+ /**
+ * Pass an tag from the I/O thread to the client thread.
+ */
+ void SetTag(Tag *_tag);
+
+ void ClearTag() {
+ SetTag(nullptr);
+ }
+
+ void Pause();
+
+ bool IsPaused() const {
+ return paused;
+ }
+
+ /**
+ * Declare that the underlying stream was closed. We will
+ * continue feeding Read() calls from the buffer until it runs
+ * empty.
+ */
+ void SetClosed() {
+ open = false;
+ }
+
+ /**
+ * Pass an error from the I/O thread to the client thread.
+ */
+ void PostponeError(Error &&error);
+
+ bool IsBufferEmpty() const {
+ return buffer.IsEmpty();
+ }
+
+ bool IsBufferFull() const {
+ return buffer.IsFull();
+ }
+
+ /**
+ * Determine how many bytes can be added to the buffer.
+ */
+ gcc_pure
+ size_t GetBufferSpace() const {
+ return buffer.GetSpace();
+ }
+
+ /**
+ * Append data to the buffer. The size must fit into the
+ * buffer; see GetBufferSpace().
+ */
+ void AppendToBuffer(const void *data, size_t append_size);
+
+ /**
+ * Implement code here that will resume the stream after it
+ * has been paused due to full input buffer.
+ */
+ virtual void DoResume() = 0;
+
+ /**
+ * The actual Seek() implementation. This virtual method will
+ * be called from within the I/O thread. When the operation
+ * is finished, call SeekDone() to notify the caller.
+ */
+ virtual void DoSeek(offset_type new_offset) = 0;
+
+ bool IsSeekPending() const {
+ return seek_state == SeekState::PENDING;
+ }
+
+ /**
+ * Call this after seeking has finished. It will notify the
+ * client thread.
+ */
+ void SeekDone();
+
+private:
+ void Resume();
+
+ /* virtual methods from DeferredMonitor */
+ void RunDeferred() final;
+};
+
+#endif
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx
deleted file mode 100644
index b3ac57413..000000000
--- a/src/input/CdioParanoiaInputPlugin.cxx
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/**
- * CD-Audio handling (requires libcdio_paranoia)
- */
-
-#include "config.h"
-#include "CdioParanoiaInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/ByteOrder.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "Log.hxx"
-#include "ConfigData.hxx"
-#include "ConfigError.hxx"
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <assert.h>
-
-#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
-#include <cdio/paranoia/paranoia.h>
-#else
-#include <cdio/paranoia.h>
-#endif
-
-#include <cdio/cd_types.h>
-
-struct CdioParanoiaInputStream {
- InputStream 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 constexpr Domain cdio_domain("cdio");
-
-static bool default_reverse_endian;
-
-static bool
-input_cdio_init(const config_param &param, Error &error)
-{
- const char *value = param.GetBlockValue("default_byte_order");
- if (value != nullptr) {
- if (strcmp(value, "little_endian") == 0)
- default_reverse_endian = IsBigEndian();
- else if (strcmp(value, "big_endian") == 0)
- default_reverse_endian = IsLittleEndian();
- else {
- error.Format(config_domain, 0,
- "Unrecognized 'default_byte_order' setting: %s",
- value);
- return false;
- }
- }
-
- return true;
-}
-
-static void
-input_cdio_close(InputStream *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, Error &error)
-{
- 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) {
- error.Set(cdio_domain, "Malformed track number");
- return false;
- }
-
- if (endptr == track)
- /* play the whole CD */
- dest->track = -1;
-
- return true;
-}
-
-static AllocatedPath
-cdio_detect_device(void)
-{
- char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
- false);
- if (devices == nullptr)
- return AllocatedPath::Null();
-
- AllocatedPath path = AllocatedPath::FromFS(devices[0]);
- cdio_free_device_list(devices);
- return path;
-}
-
-static InputStream *
-input_cdio_open(const char *uri,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- struct cdio_uri parsed_uri;
- if (!parse_cdio_uri(&parsed_uri, uri, error))
- return nullptr;
-
- CdioParanoiaInputStream *i =
- new CdioParanoiaInputStream(uri, mutex, cond,
- parsed_uri.track);
-
- /* get list of CD's supporting CD-DA */
- const AllocatedPath device = parsed_uri.device[0] != 0
- ? AllocatedPath::FromFS(parsed_uri.device)
- : cdio_detect_device();
- if (device.IsNull()) {
- error.Set(cdio_domain,
- "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.c_str(), DRIVER_UNKNOWN);
-
- i->drv = cdio_cddap_identify_cdio(i->cdio, 1, nullptr);
-
- if ( !i->drv ) {
- error.Set(cdio_domain, "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) ) {
- error.Set(cdio_domain, "Unable to open disc.");
- delete i;
- return nullptr;
- }
-
- bool reverse_endian;
- switch (data_bigendianp(i->drv)) {
- case -1:
- LogDebug(cdio_domain, "drive returns unknown audio data");
- reverse_endian = default_reverse_endian;
- break;
-
- case 0:
- LogDebug(cdio_domain, "drive returns audio data Little Endian");
- reverse_endian = IsBigEndian();
- break;
-
- case 1:
- LogDebug(cdio_domain, "drive returns audio data Big Endian");
- reverse_endian = IsLittleEndian();
- break;
-
- default:
- error.Format(cdio_domain, "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 = reverse_endian
- ? "audio/x-mpd-cdda-pcm-reverse"
- : "audio/x-mpd-cdda-pcm";
-
- return &i->base;
-}
-
-static bool
-input_cdio_seek(InputStream *is,
- InputPlugin::offset_type offset, int whence, Error &error)
-{
- 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) {
- error.Format(cdio_domain, "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(InputStream *is, void *ptr, size_t length,
- Error &error)
-{
- 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) {
- FormatError(cdio_domain,
- "paranoia_read: %s", s_err);
- free(s_err);
- }
- s_mess = cdda_messages(cis->drv);
- if (s_mess) {
- free(s_mess);
- }
- if (!rbuf) {
- error.Set(cdio_domain,
- "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(InputStream *is)
-{
- CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is;
-
- return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to);
-}
-
-const InputPlugin input_plugin_cdio_paranoia = {
- "cdio_paranoia",
- input_cdio_init,
- 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
deleted file mode 100644
index 847802a48..000000000
--- a/src/input/CdioParanoiaInputPlugin.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX
-#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_HXX
-
-/**
- * An input plugin based on libcdio_paranoia library.
- */
-extern const struct InputPlugin input_plugin_cdio_paranoia;
-
-#endif
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
deleted file mode 100644
index 031ebfea6..000000000
--- a/src/input/CurlInputPlugin.cxx
+++ /dev/null
@@ -1,1166 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "CurlInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
-#include "tag/Tag.hxx"
-#include "IcyMetaDataParser.hxx"
-#include "event/SocketMonitor.hxx"
-#include "event/TimeoutMonitor.hxx"
-#include "event/Call.hxx"
-#include "IOThread.hxx"
-#include "util/ASCII.hxx"
-#include "util/CharUtil.hxx"
-#include "util/NumberParser.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.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 <curl/curl.h>
-#include <glib.h>
-
-#if LIBCURL_VERSION_NUM < 0x071200
-#error libcurl is too old
-#endif
-
-/**
- * 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 {
- InputStream base;
-
- /* some buffers which were passed to libcurl, which we have
- too free */
- char range[32];
- 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 */
- std::string meta_name;
-
- /** the tag object ready to be requested via
- InputStream::ReadTag() */
- Tag *tag;
-
- Error postponed_error;
-
- input_curl(const char *url, Mutex &mutex, Cond &cond)
- :base(input_plugin_curl, url, mutex, cond),
- request_headers(nullptr),
- paused(false),
- tag(nullptr) {}
-
- ~input_curl();
-
- input_curl(const input_curl &) = delete;
- input_curl &operator=(const input_curl &) = delete;
-};
-
-class CurlMulti;
-
-/**
- * Monitor for one socket created by CURL.
- */
-class CurlSocket final : SocketMonitor {
- CurlMulti &multi;
-
-public:
- CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd)
- :SocketMonitor(_fd, _loop), multi(_multi) {}
-
- ~CurlSocket() {
- /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after
- closing the socket, and sometimes, it uses
- CURL_POLL_REMOVE just to move the (still open)
- connection to the pool; in the first case,
- Abandon() would be most appropriate, but it breaks
- the second case - is that a CURL bug? is there a
- better solution? */
-
- Steal();
- }
-
- /**
- * Callback function for CURLMOPT_SOCKETFUNCTION.
- */
- static int SocketFunction(CURL *easy,
- curl_socket_t s, int action,
- void *userp, void *socketp);
-
- virtual bool OnSocketReady(unsigned flags) override;
-
-private:
- static constexpr int FlagsToCurlCSelect(unsigned flags) {
- return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) |
- (flags & WRITE ? CURL_CSELECT_OUT : 0) |
- (flags & ERROR ? CURL_CSELECT_ERR : 0);
- }
-
- gcc_const
- static unsigned CurlPollToFlags(int action) {
- switch (action) {
- case CURL_POLL_NONE:
- return 0;
-
- case CURL_POLL_IN:
- return READ;
-
- case CURL_POLL_OUT:
- return WRITE;
-
- case CURL_POLL_INOUT:
- return READ|WRITE;
- }
-
- assert(false);
- gcc_unreachable();
- }
-};
-
-/**
- * Manager for the global CURLM object.
- */
-class CurlMulti final : private TimeoutMonitor {
- CURLM *const multi;
-
-public:
- CurlMulti(EventLoop &_loop, CURLM *_multi);
-
- ~CurlMulti() {
- curl_multi_cleanup(multi);
- }
-
- bool Add(input_curl *c, Error &error);
- void Remove(input_curl *c);
-
- /**
- * Check for finished HTTP responses.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
- void ReadInfo();
-
- void Assign(curl_socket_t fd, CurlSocket &cs) {
- curl_multi_assign(multi, fd, &cs);
- }
-
- void SocketAction(curl_socket_t fd, int ev_bitmask);
-
- void InvalidateSockets() {
- SocketAction(CURL_SOCKET_TIMEOUT, 0);
- }
-
- /**
- * This is a kludge to allow pausing/resuming a stream with
- * libcurl < 7.32.0. Read the curl_easy_pause manpage for
- * more information.
- */
- void ResumeSockets() {
- int running_handles;
- curl_multi_socket_all(multi, &running_handles);
- }
-
-private:
- static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
-
- virtual void OnTimeout() override;
-};
-
-/**
- * libcurl version number encoded in a 24 bit integer.
- */
-static unsigned curl_version_num;
-
-/** 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 CurlMulti *curl_multi;
-
-static constexpr Domain http_domain("http");
-static constexpr Domain curl_domain("curl");
-static constexpr Domain curlm_domain("curlm");
-
-CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi)
- :TimeoutMonitor(_loop), multi(_multi)
-{
- curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION,
- CurlSocket::SocketFunction);
- curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this);
-
- curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction);
- curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this);
-}
-
-/**
- * Find a request by its CURL "easy" handle.
- *
- * Runs in the I/O thread. No lock needed.
- */
-gcc_pure
-static struct input_curl *
-input_curl_find_request(CURL *easy)
-{
- assert(io_thread_inside());
-
- void *p;
- CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p);
- if (code != CURLE_OK)
- return nullptr;
-
- return (input_curl *)p;
-}
-
-static void
-input_curl_resume(struct input_curl *c)
-{
- assert(io_thread_inside());
-
- if (c->paused) {
- c->paused = false;
- curl_easy_pause(c->easy, CURLPAUSE_CONT);
-
- if (curl_version_num < 0x072000)
- /* libcurl older than 7.32.0 does not update
- its sockets after curl_easy_pause(); force
- libcurl to do it now */
- curl_multi->ResumeSockets();
-
- curl_multi->InvalidateSockets();
- }
-}
-
-int
-CurlSocket::SocketFunction(gcc_unused CURL *easy,
- curl_socket_t s, int action,
- void *userp, void *socketp) {
- CurlMulti &multi = *(CurlMulti *)userp;
- CurlSocket *cs = (CurlSocket *)socketp;
-
- assert(io_thread_inside());
-
- if (action == CURL_POLL_REMOVE) {
- delete cs;
- return 0;
- }
-
- if (cs == nullptr) {
- cs = new CurlSocket(multi, io_thread_get(), s);
- multi.Assign(s, *cs);
- } else {
-#ifdef USE_EPOLL
- /* when using epoll, we need to unregister the socket
- each time this callback is invoked, because older
- CURL versions may omit the CURL_POLL_REMOVE call
- when the socket has been closed and recreated with
- the same file number (bug found in CURL 7.26, CURL
- 7.33 not affected); in that case, epoll refuses the
- EPOLL_CTL_MOD because it does not know the new
- socket yet */
- cs->Cancel();
-#endif
- }
-
- unsigned flags = CurlPollToFlags(action);
- if (flags != 0)
- cs->Schedule(flags);
- return 0;
-}
-
-bool
-CurlSocket::OnSocketReady(unsigned flags)
-{
- assert(io_thread_inside());
-
- multi.SocketAction(Get(), FlagsToCurlCSelect(flags));
- return true;
-}
-
-/**
- * Runs in the I/O thread. No lock needed.
- */
-inline bool
-CurlMulti::Add(struct input_curl *c, Error &error)
-{
- assert(io_thread_inside());
- assert(c != nullptr);
- assert(c->easy != nullptr);
-
- CURLMcode mcode = curl_multi_add_handle(multi, c->easy);
- if (mcode != CURLM_OK) {
- error.Format(curlm_domain, mcode,
- "curl_multi_add_handle() failed: %s",
- curl_multi_strerror(mcode));
- return false;
- }
-
- InvalidateSockets();
- return true;
-}
-
-/**
- * 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, Error &error)
-{
- assert(c != nullptr);
- assert(c->easy != nullptr);
-
- bool result;
- BlockingCall(io_thread_get(), [c, &error, &result](){
- result = curl_multi->Add(c, error);
- });
- return result;
-}
-
-inline void
-CurlMulti::Remove(input_curl *c)
-{
- curl_multi_remove_handle(multi, c->easy);
-}
-
-/**
- * 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 != nullptr);
-
- if (c->easy == nullptr)
- return;
-
- curl_multi->Remove(c);
-
- curl_easy_cleanup(c->easy);
- c->easy = nullptr;
-
- curl_slist_free_all(c->request_headers);
- c->request_headers = nullptr;
-}
-
-/**
- * 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)
-{
- BlockingCall(io_thread_get(), [c](){
- input_curl_easy_free(c);
- curl_multi->InvalidateSockets();
- });
-
- assert(c->easy == nullptr);
-}
-
-/**
- * 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 != nullptr);
- assert(c->easy == nullptr);
- assert(!c->postponed_error.IsDefined());
-
- const ScopeLock protect(c->base.mutex);
-
- if (result != CURLE_OK) {
- c->postponed_error.Format(curl_domain, result,
- "curl failed: %s", c->error);
- } else if (status < 200 || status >= 300) {
- c->postponed_error.Format(http_domain, status,
- "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 != nullptr);
-
- long status = 0;
- curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
-
- input_curl_easy_free(c);
- input_curl_request_done(c, result, status);
-}
-
-void
-CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask)
-{
- int running_handles;
- CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask,
- &running_handles);
- if (mcode != CURLM_OK)
- FormatError(curlm_domain,
- "curl_multi_socket_action() failed: %s",
- curl_multi_strerror(mcode));
-
- ReadInfo();
-}
-
-/**
- * Check for finished HTTP responses.
- *
- * Runs in the I/O thread. The caller must not hold locks.
- */
-inline void
-CurlMulti::ReadInfo()
-{
- assert(io_thread_inside());
-
- CURLMsg *msg;
- int msgs_in_queue;
-
- while ((msg = curl_multi_info_read(multi,
- &msgs_in_queue)) != nullptr) {
- if (msg->msg == CURLMSG_DONE)
- input_curl_handle_done(msg->easy_handle, msg->data.result);
- }
-}
-
-int
-CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp)
-{
- CurlMulti &multi = *(CurlMulti *)userp;
- assert(_multi == multi.multi);
-
- if (timeout_ms < 0) {
- multi.Cancel();
- return 0;
- }
-
- if (timeout_ms >= 0 && timeout_ms < 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. */
- timeout_ms = 10;
-
- multi.Schedule(timeout_ms);
- return 0;
-}
-
-void
-CurlMulti::OnTimeout()
-{
- SocketAction(CURL_SOCKET_TIMEOUT, 0);
-}
-
-/*
- * InputPlugin methods
- *
- */
-
-static bool
-input_curl_init(const config_param &param, Error &error)
-{
- CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
- if (code != CURLE_OK) {
- error.Format(curl_domain, code,
- "curl_global_init() failed: %s",
- curl_easy_strerror(code));
- return false;
- }
-
- const auto version_info = curl_version_info(CURLVERSION_FIRST);
- if (version_info != nullptr) {
- FormatDebug(curl_domain, "version %s", version_info->version);
- if (version_info->features & CURL_VERSION_SSL)
- FormatDebug(curl_domain, "with %s",
- version_info->ssl_version);
-
- curl_version_num = version_info->version_num;
- }
-
- 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 == nullptr) {
- /* deprecated proxy configuration */
- proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr);
- proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
- proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr);
- proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
- "");
- }
-
- CURLM *multi = curl_multi_init();
- if (multi == nullptr) {
- error.Set(curl_domain, 0, "curl_multi_init() failed");
- return false;
- }
-
- curl_multi = new CurlMulti(io_thread_get(), multi);
- return true;
-}
-
-static void
-input_curl_finish(void)
-{
- BlockingCall(io_thread_get(), [](){
- delete 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.
- */
-gcc_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;
-
- input_curl_easy_free_indirect(this);
-}
-
-static bool
-input_curl_check(InputStream *is, Error &error)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- bool success = !c->postponed_error.IsDefined();
- if (!success) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
- }
-
- return success;
-}
-
-static Tag *
-input_curl_tag(InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
- Tag *tag = c->tag;
-
- c->tag = nullptr;
- return tag;
-}
-
-static bool
-fill_buffer(struct input_curl *c, Error &error)
-{
- while (c->easy != nullptr && c->buffers.empty())
- c->base.cond.wait(c->base.mutex);
-
- if (c->postponed_error.IsDefined()) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
- 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 == nullptr)
- return;
-
- delete c->tag;
-
- if (!c->meta_name.empty() && !tag->HasType(TAG_NAME))
- tag->AddItem(TAG_NAME, c->meta_name.c_str());
-
- c->tag = tag;
-}
-
-static bool
-input_curl_available(InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->postponed_error.IsDefined() || c->easy == nullptr ||
- !c->buffers.empty();
-}
-
-static size_t
-input_curl_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- 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);
- 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 += (InputPlugin::offset_type)nbytes;
-
- if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
- c->base.mutex.unlock();
-
- BlockingCall(io_thread_get(), [c](){
- input_curl_resume(c);
- });
-
- c->base.mutex.lock();
- }
-
- return nbytes;
-}
-
-static void
-input_curl_close(InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- delete c;
-}
-
-static bool
-input_curl_eof(gcc_unused InputStream *is)
-{
- struct input_curl *c = (struct input_curl *)is;
-
- return c->easy == nullptr && 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 == nullptr || (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 && IsWhitespaceOrNull(*value))
- ++value;
-
- while (end > value && IsWhitespaceOrNull(end[-1]))
- --end;
-
- if (StringEqualsCaseASCII(name, "accept-ranges")) {
- /* a stream with icy-metadata is not seekable */
- if (!c->icy.IsDefined())
- c->base.seekable = true;
- } else if (StringEqualsCaseASCII(name, "content-length")) {
- 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 + ParseUint64(buffer);
- } else if (StringEqualsCaseASCII(name, "content-type")) {
- c->base.mime.assign(value, end);
- } else if (StringEqualsCaseASCII(name, "icy-name") ||
- StringEqualsCaseASCII(name, "ice-name") ||
- StringEqualsCaseASCII(name, "x-audiocast-name")) {
- c->meta_name.assign(value, end);
-
- delete c->tag;
-
- c->tag = new Tag();
- c->tag->AddItem(TAG_NAME, c->meta_name.c_str());
- } else if (StringEqualsCaseASCII(name, "icy-metaint")) {
- 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 = ParseUint64(buffer);
- FormatDebug(curl_domain, "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, Error &error)
-{
- CURLcode code;
-
- c->easy = curl_easy_init();
- if (c->easy == nullptr) {
- error.Set(curl_domain, "curl_easy_init() failed");
- return false;
- }
-
- curl_easy_setopt(c->easy, CURLOPT_PRIVATE, (void *)c);
- 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, 1l);
- curl_easy_setopt(c->easy, CURLOPT_NETRC, 1l);
- curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5l);
- curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, 1l);
- 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 != nullptr)
- 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 != nullptr && proxy_password != nullptr) {
- char proxy_auth_str[1024];
- snprintf(proxy_auth_str, sizeof(proxy_auth_str),
- "%s:%s",
- proxy_user, proxy_password);
- curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
- }
-
- code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str());
- if (code != CURLE_OK) {
- error.Format(curl_domain, code,
- "curl_easy_setopt() failed: %s",
- curl_easy_strerror(code));
- return false;
- }
-
- c->request_headers = nullptr;
- 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(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- 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 < (InputPlugin::offset_type)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);
- if (!ret)
- return false;
-
- /* send the "Range" header */
-
- if (is->offset > 0) {
- sprintf(c->range, "%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))
- return false;
-
- c->base.mutex.lock();
-
- while (!c->base.ready)
- c->base.cond.wait(c->base.mutex);
-
- if (c->postponed_error.IsDefined()) {
- error = std::move(c->postponed_error);
- c->postponed_error.Clear();
- return false;
- }
-
- return true;
-}
-
-static InputStream *
-input_curl_open(const char *url, Mutex &mutex, Cond &cond,
- Error &error)
-{
- if (memcmp(url, "http://", 7) != 0 &&
- memcmp(url, "https://", 8) != 0)
- return nullptr;
-
- struct input_curl *c = new input_curl(url, mutex, cond);
-
- if (!input_curl_easy_init(c, error)) {
- delete c;
- return nullptr;
- }
-
- if (!input_curl_easy_add_indirect(c, error)) {
- delete c;
- return nullptr;
- }
-
- return &c->base;
-}
-
-const struct InputPlugin 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
deleted file mode 100644
index 30e917257..000000000
--- a/src/input/CurlInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INPUT_CURL_HXX
-#define MPD_INPUT_CURL_HXX
-
-extern const struct InputPlugin input_plugin_curl;
-
-#endif
diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx
deleted file mode 100644
index b08299516..000000000
--- a/src/input/DespotifyInputPlugin.cxx
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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 "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "tag/Tag.hxx"
-#include "Log.hxx"
-
-extern "C" {
-#include <despotify.h>
-}
-
-#include <glib.h>
-
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-
-#include <stdio.h>
-
-struct DespotifyInputStream {
- InputStream 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 = "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) {
- LogDebug(despotify_domain, "despotify_get_pcm error");
- ctx->eof = true;
- break;
- }
-
- /* Wait a while until next iteration */
- usleep(50 * 1000);
- }
-}
-
-static void callback(gcc_unused struct despotify_session* ds,
- int sig, gcc_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:
- LogWarning(despotify_domain, "Track play error");
- ctx->eof = true;
- ctx->len_available = 0;
- break;
-
- case DESPOTIFY_END_OF_PLAYLIST:
- ctx->eof = true;
- FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof);
- break;
- }
-}
-
-
-static InputStream *
-input_despotify_open(const char *url,
- Mutex &mutex, Cond &cond,
- gcc_unused Error &error)
-{
- struct despotify_session *session;
- struct ds_link *ds_link;
- struct ds_track *track;
-
- if (!g_str_has_prefix(url, "spt://"))
- return nullptr;
-
- session = mpd_despotify_get_session();
- if (!session)
- return nullptr;
-
- ds_link = despotify_link_from_uri(url + 6);
- if (!ds_link) {
- FormatDebug(despotify_domain, "Can't find %s", url);
- return nullptr;
- }
- if (ds_link->type != LINK_TYPE_TRACK) {
- despotify_free_link(ds_link);
- return nullptr;
- }
-
- track = despotify_link_get_track(session, ds_link);
- despotify_free_link(ds_link);
- if (!track)
- return nullptr;
-
- DespotifyInputStream *ctx =
- new DespotifyInputStream(url, mutex, cond,
- session, track);
-
- if (!mpd_despotify_register_callback(callback, ctx)) {
- delete ctx;
- return nullptr;
- }
-
- if (despotify_play(ctx->session, ctx->track, false) == false) {
- mpd_despotify_unregister_callback(callback);
- delete ctx;
- return nullptr;
- }
-
- return &ctx->base;
-}
-
-static size_t
-input_despotify_read(InputStream *is, void *ptr, size_t size,
- gcc_unused Error &error)
-{
- 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(InputStream *is)
-{
- DespotifyInputStream *ctx = (DespotifyInputStream *)is;
-
- mpd_despotify_unregister_callback(callback);
- delete ctx;
-}
-
-static bool
-input_despotify_eof(InputStream *is)
-{
- DespotifyInputStream *ctx = (DespotifyInputStream *)is;
-
- return ctx->eof;
-}
-
-static Tag *
-input_despotify_tag(InputStream *is)
-{
- DespotifyInputStream *ctx = (DespotifyInputStream *)is;
- Tag *tag = ctx->tag;
-
- ctx->tag = nullptr;
-
- return tag;
-}
-
-const InputPlugin input_plugin_despotify = {
- "spt",
- nullptr,
- nullptr,
- input_despotify_open,
- input_despotify_close,
- nullptr,
- nullptr,
- input_despotify_tag,
- nullptr,
- input_despotify_read,
- input_despotify_eof,
- nullptr,
-};
diff --git a/src/input/DespotifyInputPlugin.hxx b/src/input/DespotifyInputPlugin.hxx
deleted file mode 100644
index f1911f235..000000000
--- a/src/input/DespotifyInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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 InputPlugin input_plugin_despotify;
-
-#endif
diff --git a/src/input/Domain.cxx b/src/input/Domain.cxx
new file mode 100644
index 000000000..26ae298a4
--- /dev/null
+++ b/src/input/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain input_domain("input");
diff --git a/src/input/Domain.hxx b/src/input/Domain.hxx
new file mode 100644
index 000000000..16fa5e0f1
--- /dev/null
+++ b/src/input/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_DOMAIN_HXX
+#define MPD_INPUT_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain input_domain;
+
+#endif
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx
deleted file mode 100644
index 8f9cd0b86..000000000
--- a/src/input/FfmpegInputPlugin.cxx
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/* necessary because libavutil/common.h uses UINT64_C */
-#define __STDC_CONSTANT_MACROS
-
-#include "config.h"
-#include "FfmpegInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-extern "C" {
-#include <libavutil/avutil.h>
-#include <libavformat/avio.h>
-#include <libavformat/avformat.h>
-}
-
-#include <glib.h>
-
-struct FfmpegInputStream {
- InputStream base;
-
- 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 = "audio/x-mpd-ffmpeg";
- }
-
- ~FfmpegInputStream() {
- avio_close(h);
- }
-};
-
-static constexpr Domain ffmpeg_domain("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,
- Error &error)
-{
- av_register_all();
-
- /* disable this plugin if there's no registered protocol */
- if (!input_ffmpeg_supported()) {
- error.Set(ffmpeg_domain, "No protocol");
- return false;
- }
-
- return true;
-}
-
-static InputStream *
-input_ffmpeg_open(const char *uri,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- if (!g_str_has_prefix(uri, "gopher://") &&
- !g_str_has_prefix(uri, "rtp://") &&
- !g_str_has_prefix(uri, "rtsp://") &&
- !g_str_has_prefix(uri, "rtmp://") &&
- !g_str_has_prefix(uri, "rtmpt://") &&
- !g_str_has_prefix(uri, "rtmps://"))
- return nullptr;
-
- AVIOContext *h;
- int ret = avio_open(&h, uri, AVIO_FLAG_READ);
- if (ret != 0) {
- error.Set(ffmpeg_domain, 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(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
-
- int ret = avio_read(i->h, (unsigned char *)ptr, size);
- if (ret <= 0) {
- if (ret < 0)
- error.Set(ffmpeg_domain, "avio_read() failed");
-
- i->eof = true;
- return false;
- }
-
- is->offset += ret;
- return (size_t)ret;
-}
-
-static void
-input_ffmpeg_close(InputStream *is)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
-
- delete i;
-}
-
-static bool
-input_ffmpeg_eof(InputStream *is)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
-
- return i->eof;
-}
-
-static bool
-input_ffmpeg_seek(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- FfmpegInputStream *i = (FfmpegInputStream *)is;
- int64_t ret = avio_seek(i->h, offset, whence);
-
- if (ret >= 0) {
- i->eof = false;
- return true;
- } else {
- error.Set(ffmpeg_domain, "avio_seek() failed");
- return false;
- }
-}
-
-const InputPlugin 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
deleted file mode 100644
index 9bc2eeaea..000000000
--- a/src/input/FfmpegInputPlugin.hxx
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX
-#define MPD_FFMPEG_INPUT_PLUGIN_HXX
-
-/**
- * An input plugin based on libavformat's "avio" library.
- */
-extern const struct InputPlugin input_plugin_ffmpeg;
-
-#endif
diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx
deleted file mode 100644
index 26e40d609..000000000
--- a/src/input/FileInputPlugin.cxx
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h" /* must be first for large file support */
-#include "FileInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "fs/Traits.hxx"
-#include "system/fd_util.h"
-#include "open.h"
-
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-#include <glib.h>
-
-static constexpr Domain file_domain("file");
-
-struct FileInputStream {
- InputStream 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 InputStream *
-input_file_open(const char *filename,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- int fd, ret;
- struct stat st;
-
- if (!PathTraits::IsAbsoluteFS(filename))
- return nullptr;
-
- fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
- if (fd < 0) {
- if (errno != ENOENT && errno != ENOTDIR)
- error.FormatErrno("Failed to open \"%s\"",
- filename);
- return nullptr;
- }
-
- ret = fstat(fd, &st);
- if (ret < 0) {
- error.FormatErrno("Failed to stat \"%s\"", filename);
- close(fd);
- return nullptr;
- }
-
- if (!S_ISREG(st.st_mode)) {
- error.Format(file_domain, "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(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- FileInputStream *fis = (FileInputStream *)is;
-
- offset = (InputPlugin::offset_type)lseek(fis->fd, (off_t)offset, whence);
- if (offset < 0) {
- error.SetErrno("Failed to seek");
- return false;
- }
-
- is->offset = offset;
- return true;
-}
-
-static size_t
-input_file_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- FileInputStream *fis = (FileInputStream *)is;
- ssize_t nbytes;
-
- nbytes = read(fis->fd, ptr, size);
- if (nbytes < 0) {
- error.SetErrno("Failed to read");
- return 0;
- }
-
- is->offset += nbytes;
- return (size_t)nbytes;
-}
-
-static void
-input_file_close(InputStream *is)
-{
- FileInputStream *fis = (FileInputStream *)is;
-
- delete fis;
-}
-
-static bool
-input_file_eof(InputStream *is)
-{
- return is->offset >= is->size;
-}
-
-const InputPlugin 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
deleted file mode 100644
index f76f4dd0e..000000000
--- a/src/input/FileInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_INPUT_FILE_HXX
-#define MPD_INPUT_FILE_HXX
-
-extern const struct InputPlugin input_plugin_file;
-
-#endif
diff --git a/src/input/IcyInputStream.cxx b/src/input/IcyInputStream.cxx
new file mode 100644
index 000000000..fb82cdec6
--- /dev/null
+++ b/src/input/IcyInputStream.cxx
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "IcyInputStream.hxx"
+#include "tag/Tag.hxx"
+
+IcyInputStream::IcyInputStream(InputStream *_input)
+ :ProxyInputStream(_input),
+ input_tag(nullptr), icy_tag(nullptr),
+ override_offset(0)
+{
+}
+
+IcyInputStream::~IcyInputStream()
+{
+ delete input_tag;
+ delete icy_tag;
+}
+
+void
+IcyInputStream::Update()
+{
+ ProxyInputStream::Update();
+
+ if (IsEnabled())
+ offset = override_offset;
+}
+
+Tag *
+IcyInputStream::ReadTag()
+{
+ Tag *new_input_tag = ProxyInputStream::ReadTag();
+ if (!IsEnabled())
+ return new_input_tag;
+
+ if (new_input_tag != nullptr) {
+ delete input_tag;
+ input_tag = new_input_tag;
+ }
+
+ Tag *new_icy_tag = parser.ReadTag();
+ if (new_icy_tag != nullptr) {
+ delete icy_tag;
+ icy_tag = new_icy_tag;
+ }
+
+ if (new_input_tag == nullptr && new_icy_tag == nullptr)
+ /* no change */
+ return nullptr;
+
+ if (input_tag == nullptr && icy_tag == nullptr)
+ /* no tag */
+ return nullptr;
+
+ if (input_tag == nullptr)
+ return new Tag(*icy_tag);
+
+ if (icy_tag == nullptr)
+ return new Tag(*input_tag);
+
+ return Tag::Merge(*input_tag, *icy_tag);
+}
+
+size_t
+IcyInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ if (!IsEnabled())
+ return ProxyInputStream::Read(ptr, read_size, error);
+
+ while (true) {
+ size_t nbytes = ProxyInputStream::Read(ptr, read_size, error);
+ if (nbytes == 0)
+ return 0;
+
+ size_t result = parser.ParseInPlace(ptr, nbytes);
+ if (result > 0) {
+ override_offset += result;
+ offset = override_offset;
+ return result;
+ }
+ }
+}
diff --git a/src/input/IcyInputStream.hxx b/src/input/IcyInputStream.hxx
new file mode 100644
index 000000000..d8968a741
--- /dev/null
+++ b/src/input/IcyInputStream.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICY_INPUT_STREAM_HXX
+#define MPD_ICY_INPUT_STREAM_HXX
+
+#include "ProxyInputStream.hxx"
+#include "IcyMetaDataParser.hxx"
+#include "Compiler.h"
+
+struct Tag;
+
+/**
+ * An #InputStream filter that parses Icy metadata.
+ */
+class IcyInputStream final : public ProxyInputStream {
+ IcyMetaDataParser parser;
+
+ /**
+ * The #Tag object ready to be requested via ReadTag().
+ */
+ Tag *input_tag;
+
+ /**
+ * The #Tag object ready to be requested via ReadTag().
+ */
+ Tag *icy_tag;
+
+ offset_type override_offset;
+
+public:
+ IcyInputStream(InputStream *_input);
+ virtual ~IcyInputStream();
+
+ IcyInputStream(const IcyInputStream &) = delete;
+ IcyInputStream &operator=(const IcyInputStream &) = delete;
+
+ void Enable(size_t _data_size) {
+ parser.Start(_data_size);
+ }
+
+ bool IsEnabled() const {
+ return parser.IsDefined();
+ }
+
+ /* virtual methods from InputStream */
+ void Update() override;
+ Tag *ReadTag() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/input/Init.cxx b/src/input/Init.cxx
new file mode 100644
index 000000000..0ee87c4d8
--- /dev/null
+++ b/src/input/Init.cxx
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Registry.hxx"
+#include "InputPlugin.hxx"
+#include "util/Error.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "config/ConfigData.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+bool
+input_stream_global_init(Error &error)
+{
+ const config_param empty;
+
+ for (unsigned i = 0; input_plugins[i] != nullptr; ++i) {
+ const InputPlugin *plugin = input_plugins[i];
+
+ assert(plugin->name != nullptr);
+ assert(*plugin->name != 0);
+ assert(plugin->open != nullptr);
+
+ const struct config_param *param =
+ config_find_block(CONF_INPUT, "plugin", plugin->name);
+ if (param == nullptr) {
+ param = &empty;
+ } else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ InputPlugin::InitResult result = plugin->init != nullptr
+ ? plugin->init(*param, error)
+ : InputPlugin::InitResult::SUCCESS;
+
+ switch (result) {
+ case InputPlugin::InitResult::SUCCESS:
+ input_plugins_enabled[i] = true;
+ break;
+
+ case InputPlugin::InitResult::ERROR:
+ error.FormatPrefix("Failed to initialize input plugin '%s': ",
+ plugin->name);
+ return false;
+
+ case InputPlugin::InitResult::UNAVAILABLE:
+ if (error.IsDefined()) {
+ FormatError(error,
+ "Input plugin '%s' is unavailable",
+ plugin->name);
+ error.Clear();
+ }
+
+ break;
+ }
+ }
+
+ return true;
+}
+
+void input_stream_global_finish(void)
+{
+ input_plugins_for_each_enabled(plugin)
+ if (plugin->finish != nullptr)
+ plugin->finish();
+}
diff --git a/src/input/Init.hxx b/src/input/Init.hxx
new file mode 100644
index 000000000..875fdce7c
--- /dev/null
+++ b/src/input/Init.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_INIT_HXX
+#define MPD_INPUT_INIT_HXX
+
+class Error;
+
+/**
+ * Initializes this library and all input_stream implementations.
+ */
+bool
+input_stream_global_init(Error &error);
+
+/**
+ * Deinitializes this library and all input_stream implementations.
+ */
+void input_stream_global_finish(void);
+
+#endif
diff --git a/src/input/InputPlugin.hxx b/src/input/InputPlugin.hxx
new file mode 100644
index 000000000..c2adb419c
--- /dev/null
+++ b/src/input/InputPlugin.hxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_PLUGIN_HXX
+#define MPD_INPUT_PLUGIN_HXX
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef WIN32
+#include <windows.h>
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+struct config_param;
+class InputStream;
+class Error;
+struct Tag;
+
+struct InputPlugin {
+ enum class InitResult {
+ /**
+ * A fatal error has occurred (e.g. misconfiguration).
+ * The #Error has been set.
+ */
+ ERROR,
+
+ /**
+ * The plugin was initialized successfully and is
+ * ready to be used.
+ */
+ SUCCESS,
+
+ /**
+ * The plugin is not available and shall be disabled.
+ * The #Error may be set describing the situation (to
+ * be logged).
+ */
+ UNAVAILABLE,
+ };
+
+ const char *name;
+
+ /**
+ * Global initialization. This method is called when MPD starts.
+ *
+ * @return true on success, false if the plugin should be
+ * disabled
+ */
+ InitResult (*init)(const config_param &param, Error &error);
+
+ /**
+ * Global deinitialization. Called once before MPD shuts
+ * down (only if init() has returned true).
+ */
+ void (*finish)(void);
+
+ InputStream *(*open)(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+};
+
+#endif
diff --git a/src/input/InputStream.cxx b/src/input/InputStream.cxx
new file mode 100644
index 000000000..44f726a62
--- /dev/null
+++ b/src/input/InputStream.cxx
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "InputStream.hxx"
+#include "thread/Cond.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+InputStream::~InputStream()
+{
+}
+
+bool
+InputStream::Check(gcc_unused Error &error)
+{
+ return true;
+}
+
+void
+InputStream::Update()
+{
+}
+
+void
+InputStream::SetReady()
+{
+ assert(!ready);
+
+ ready = true;
+ cond.broadcast();
+}
+
+void
+InputStream::WaitReady()
+{
+ while (true) {
+ Update();
+ if (ready)
+ break;
+
+ cond.wait(mutex);
+ }
+}
+
+void
+InputStream::LockWaitReady()
+{
+ const ScopeLock protect(mutex);
+ WaitReady();
+}
+
+/**
+ * Is seeking on resources behind this URI "expensive"? For example,
+ * seeking in a HTTP file requires opening a new connection with a new
+ * HTTP request.
+ */
+gcc_pure
+static bool
+ExpensiveSeeking(const char *uri)
+{
+ return StringStartsWith(uri, "http://") ||
+ StringStartsWith(uri, "https://");
+}
+
+bool
+InputStream::CheapSeeking() const
+{
+ return IsSeekable() && !ExpensiveSeeking(uri.c_str());
+}
+
+bool
+InputStream::Seek(gcc_unused offset_type new_offset,
+ gcc_unused Error &error)
+{
+ return false;
+}
+
+bool
+InputStream::LockSeek(offset_type _offset, Error &error)
+{
+ const ScopeLock protect(mutex);
+ return Seek(_offset, error);
+}
+
+Tag *
+InputStream::ReadTag()
+{
+ return nullptr;
+}
+
+Tag *
+InputStream::LockReadTag()
+{
+ const ScopeLock protect(mutex);
+ return ReadTag();
+}
+
+bool
+InputStream::IsAvailable()
+{
+ return true;
+}
+
+size_t
+InputStream::LockRead(void *ptr, size_t _size, Error &error)
+{
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
+ assert(ptr != nullptr);
+#endif
+ assert(_size > 0);
+
+ const ScopeLock protect(mutex);
+ return Read(ptr, _size, error);
+}
+
+bool
+InputStream::LockIsEOF()
+{
+ const ScopeLock protect(mutex);
+ return IsEOF();
+}
+
diff --git a/src/input/InputStream.hxx b/src/input/InputStream.hxx
new file mode 100644
index 000000000..81b903ba2
--- /dev/null
+++ b/src/input/InputStream.hxx
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_STREAM_HXX
+#define MPD_INPUT_STREAM_HXX
+
+#include "check.h"
+#include "Offset.hxx"
+#include "thread/Mutex.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+#include <assert.h>
+#include <stdint.h>
+
+class Cond;
+class Error;
+struct Tag;
+
+class InputStream {
+public:
+ typedef ::offset_type offset_type;
+
+private:
+ /**
+ * The absolute URI which was used to open this stream.
+ */
+ std::string uri;
+
+public:
+ /**
+ * 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 nullptr.
+ *
+ * This object is allocated by the client, and the client is
+ * responsible for freeing it.
+ */
+ Cond &cond;
+
+protected:
+ /**
+ * 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;
+
+ static constexpr offset_type UNKNOWN_SIZE = -1;
+
+ /**
+ * the size of the resource, or #UNKNOWN_SIZE if unknown
+ */
+ offset_type size;
+
+ /**
+ * the current offset within the stream
+ */
+ offset_type offset;
+
+private:
+ /**
+ * the MIME content type of the resource, or empty if unknown.
+ */
+ std::string mime;
+
+public:
+ InputStream(const char *_uri, Mutex &_mutex, Cond &_cond)
+ :uri(_uri),
+ mutex(_mutex), cond(_cond),
+ ready(false), seekable(false),
+ size(UNKNOWN_SIZE), offset(0) {
+ assert(_uri != nullptr);
+ }
+
+ /**
+ * Close the input stream and free resources.
+ *
+ * The caller must not lock the mutex.
+ */
+ virtual ~InputStream();
+
+ /**
+ * Opens a new input stream. You may not access it until the "ready"
+ * flag is set.
+ *
+ * @param mutex a mutex that is used to protect this object; must be
+ * locked before calling any of the public methods
+ * @param cond a cond that gets signalled when the state of
+ * this object changes; may be nullptr if the caller doesn't want to get
+ * notifications
+ * @return an #InputStream object on success, nullptr on error
+ */
+ gcc_nonnull_all
+ gcc_malloc
+ static InputStream *Open(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /**
+ * Just like Open(), but waits for the stream to become ready.
+ * It is a wrapper for Open(), WaitReady() and Check().
+ */
+ gcc_malloc gcc_nonnull_all
+ static InputStream *OpenReady(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /**
+ * The absolute URI which was used to open this stream.
+ *
+ * No lock necessary for this method.
+ */
+ const char *GetURI() const {
+ return uri.c_str();
+ }
+
+ void Lock() {
+ mutex.lock();
+ }
+
+ void Unlock() {
+ mutex.unlock();
+ }
+
+ /**
+ * Check for errors that may have occurred in the I/O thread.
+ *
+ * @return false on error
+ */
+ virtual bool Check(Error &error);
+
+ /**
+ * Update the public attributes. Call before accessing attributes
+ * such as "ready" or "offset".
+ */
+ virtual void Update();
+
+ void SetReady();
+
+ /**
+ * Return whether the stream is ready for reading and whether
+ * the other attributes in this struct are valid.
+ *
+ * The caller must lock the mutex.
+ */
+ bool IsReady() const {
+ return ready;
+ }
+
+ void WaitReady();
+
+ /**
+ * Wrapper for WaitReady() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ void LockWaitReady();
+
+ gcc_pure
+ bool HasMimeType() const {
+ assert(ready);
+
+ return !mime.empty();
+ }
+
+ gcc_pure
+ const char *GetMimeType() const {
+ assert(ready);
+
+ return mime.empty() ? nullptr : mime.c_str();
+ }
+
+ void ClearMimeType() {
+ mime.clear();
+ }
+
+ gcc_nonnull_all
+ void SetMimeType(const char *_mime) {
+ assert(!ready);
+
+ mime = _mime;
+ }
+
+ void SetMimeType(std::string &&_mime) {
+ assert(!ready);
+
+ mime = std::move(_mime);
+ }
+
+ gcc_nonnull_all
+ void OverrideMimeType(const char *_mime) {
+ assert(ready);
+
+ mime = _mime;
+ }
+
+ gcc_pure
+ bool KnownSize() const {
+ assert(ready);
+
+ return size != UNKNOWN_SIZE;
+ }
+
+ gcc_pure
+ offset_type GetSize() const {
+ assert(ready);
+ assert(KnownSize());
+
+ return size;
+ }
+
+ void AddOffset(offset_type delta) {
+ assert(ready);
+
+ offset += delta;
+ }
+
+ gcc_pure
+ offset_type GetOffset() const {
+ assert(ready);
+
+ return offset;
+ }
+
+ gcc_pure
+ offset_type GetRest() const {
+ assert(ready);
+ assert(KnownSize());
+
+ return size - offset;
+ }
+
+ gcc_pure
+ bool IsSeekable() const {
+ assert(ready);
+
+ return seekable;
+ }
+
+ /**
+ * Determines whether seeking is cheap. This is true for local files.
+ */
+ gcc_pure
+ bool CheapSeeking() const;
+
+ /**
+ * Seeks to the specified position in the stream. This will most
+ * likely fail if the "seekable" flag is false.
+ *
+ * The caller must lock the mutex.
+ *
+ * @param offset the relative offset
+ */
+ virtual bool Seek(offset_type offset, Error &error);
+
+ /**
+ * Wrapper for Seek() which locks and unlocks the mutex; the
+ * caller must not be holding it already.
+ */
+ bool LockSeek(offset_type offset, Error &error);
+
+ /**
+ * Rewind to the beginning of the stream. This is a wrapper
+ * for Seek(0, error).
+ */
+ bool Rewind(Error &error) {
+ return Seek(0, error);
+ }
+
+ bool LockRewind(Error &error) {
+ return LockSeek(0, error);
+ }
+
+ /**
+ * Returns true if the stream has reached end-of-file.
+ *
+ * The caller must lock the mutex.
+ */
+ gcc_pure
+ virtual bool IsEOF() = 0;
+
+ /**
+ * Wrapper for IsEOF() which locks and unlocks the mutex; the
+ * caller must not be holding it already.
+ */
+ gcc_pure
+ bool LockIsEOF();
+
+ /**
+ * Reads the tag from the stream.
+ *
+ * The caller must lock the mutex.
+ *
+ * @return a tag object which must be freed by the caller, or
+ * nullptr if the tag has not changed since the last call
+ */
+ gcc_malloc
+ virtual Tag *ReadTag();
+
+ /**
+ * Wrapper for ReadTag() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ gcc_malloc
+ Tag *LockReadTag();
+
+ /**
+ * 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.
+ *
+ * The caller must lock the mutex.
+ */
+ gcc_pure
+ virtual bool IsAvailable();
+
+ /**
+ * Reads data from the stream into the caller-supplied buffer.
+ * Returns 0 on error or eof (check with IsEOF()).
+ *
+ * The caller must lock the mutex.
+ *
+ * @param is the InputStream object
+ * @param ptr the buffer to read into
+ * @param size the maximum number of bytes to read
+ * @return the number of bytes read
+ */
+ gcc_nonnull_all
+ virtual size_t Read(void *ptr, size_t size, Error &error) = 0;
+
+ /**
+ * Wrapper for Read() which locks and unlocks the mutex;
+ * the caller must not be holding it already.
+ */
+ gcc_nonnull_all
+ size_t LockRead(void *ptr, size_t size, Error &error);
+};
+
+#endif
diff --git a/src/input/LocalOpen.cxx b/src/input/LocalOpen.cxx
new file mode 100644
index 000000000..ad8eba8ce
--- /dev/null
+++ b/src/input/LocalOpen.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LocalOpen.hxx"
+#include "InputStream.hxx"
+#include "plugins/FileInputPlugin.hxx"
+
+#ifdef ENABLE_ARCHIVE
+#include "plugins/ArchiveInputPlugin.hxx"
+#endif
+
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+#ifdef ENABLE_ARCHIVE
+#include <errno.h>
+#endif
+
+InputStream *
+OpenLocalInputStream(Path path, Mutex &mutex, Cond &cond, Error &error)
+{
+ assert(!error.IsDefined());
+
+ InputStream *is = OpenFileInputStream(path, mutex, cond, error);
+#ifdef ENABLE_ARCHIVE
+ if (is == nullptr && error.IsDomain(errno_domain) &&
+ error.GetCode() == ENOTDIR) {
+ /* ENOTDIR means this may be a path inside an archive
+ file */
+ Error error2;
+ is = OpenArchiveInputStream(path, mutex, cond, error2);
+ if (is == nullptr && error2.IsDefined())
+ error = std::move(error2);
+ }
+#endif
+
+ assert(is == nullptr || is->IsReady());
+
+ return is;
+}
diff --git a/src/input/LocalOpen.hxx b/src/input/LocalOpen.hxx
new file mode 100644
index 000000000..cf1b2b632
--- /dev/null
+++ b/src/input/LocalOpen.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_LOCAL_OPEN_HXX
+#define MPD_INPUT_LOCAL_OPEN_HXX
+
+#include "check.h"
+
+class InputStream;
+class Path;
+class Mutex;
+class Cond;
+class Error;
+
+/**
+ * Open a "local" file. This is a wrapper for the input plugins
+ * "file" and "archive".
+ */
+InputStream *
+OpenLocalInputStream(Path path, Mutex &mutex, Cond &cond, Error &error);
+
+#endif
diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx
deleted file mode 100644
index e97c1eb3f..000000000
--- a/src/input/MmsInputPlugin.cxx
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MmsInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <glib.h>
-#include <libmms/mmsx.h>
-
-#include <string.h>
-#include <errno.h>
-
-struct MmsInputStream {
- InputStream base;
-
- 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 = "audio/x-ms-wma";
-
- base.ready = true;
- }
-
- ~MmsInputStream() {
- mmsx_close(mms);
- }
-};
-
-static constexpr Domain mms_domain("mms");
-
-static InputStream *
-input_mms_open(const char *url,
- Mutex &mutex, Cond &cond,
- Error &error)
-{
- if (!g_str_has_prefix(url, "mms://") &&
- !g_str_has_prefix(url, "mmsh://") &&
- !g_str_has_prefix(url, "mmst://") &&
- !g_str_has_prefix(url, "mmsu://"))
- return nullptr;
-
- const auto mms = mmsx_connect(nullptr, nullptr, url, 128 * 1024);
- if (mms == nullptr) {
- error.Set(mms_domain, "mmsx_connect() failed");
- return nullptr;
- }
-
- auto m = new MmsInputStream(url, mutex, cond, mms);
- return &m->base;
-}
-
-static size_t
-input_mms_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- MmsInputStream *m = (MmsInputStream *)is;
- int ret;
-
- ret = mmsx_read(nullptr, m->mms, (char *)ptr, size);
- if (ret <= 0) {
- if (ret < 0)
- error.SetErrno("mmsx_read() failed");
-
- m->eof = true;
- return false;
- }
-
- is->offset += ret;
-
- return (size_t)ret;
-}
-
-static void
-input_mms_close(InputStream *is)
-{
- MmsInputStream *m = (MmsInputStream *)is;
-
- delete m;
-}
-
-static bool
-input_mms_eof(InputStream *is)
-{
- MmsInputStream *m = (MmsInputStream *)is;
-
- return m->eof;
-}
-
-const InputPlugin input_plugin_mms = {
- "mms",
- nullptr,
- nullptr,
- input_mms_open,
- input_mms_close,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- input_mms_read,
- input_mms_eof,
- nullptr,
-};
diff --git a/src/input/MmsInputPlugin.hxx b/src/input/MmsInputPlugin.hxx
deleted file mode 100644
index e3d3ba3c8..000000000
--- a/src/input/MmsInputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef INPUT_MMS_H
-#define INPUT_MMS_H
-
-extern const struct InputPlugin input_plugin_mms;
-
-#endif
diff --git a/src/input/Offset.hxx b/src/input/Offset.hxx
new file mode 100644
index 000000000..552397904
--- /dev/null
+++ b/src/input/Offset.hxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OFFSET_HXX
+#define MPD_OFFSET_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+
+/**
+ * A type for absolute offsets in a file.
+ */
+typedef uint64_t offset_type;
+
+#endif
diff --git a/src/input/Open.cxx b/src/input/Open.cxx
new file mode 100644
index 000000000..66ccdce74
--- /dev/null
+++ b/src/input/Open.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "InputStream.hxx"
+#include "Registry.hxx"
+#include "InputPlugin.hxx"
+#include "LocalOpen.hxx"
+#include "Domain.hxx"
+#include "plugins/RewindInputPlugin.hxx"
+#include "fs/Traits.hxx"
+#include "fs/Path.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+InputStream *
+InputStream::Open(const char *url,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (PathTraitsFS::IsAbsolute(url))
+ /* TODO: the parameter is UTF-8, not filesystem charset */
+ return OpenLocalInputStream(Path::FromFS(url),
+ mutex, cond, error);
+
+ input_plugins_for_each_enabled(plugin) {
+ InputStream *is;
+
+ is = plugin->open(url, mutex, cond, error);
+ if (is != nullptr) {
+ is = input_rewind_open(is);
+
+ return is;
+ } else if (error.IsDefined())
+ return nullptr;
+ }
+
+ error.Set(input_domain, "Unrecognized URI");
+ return nullptr;
+}
+
+InputStream *
+InputStream::OpenReady(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ InputStream *is = Open(uri, mutex, cond, error);
+ if (is == nullptr)
+ return nullptr;
+
+ mutex.lock();
+ is->WaitReady();
+ bool success = is->Check(error);
+ mutex.unlock();
+
+ if (!success) {
+ delete is;
+ is = nullptr;
+ }
+
+ return is;
+}
diff --git a/src/input/ProxyInputStream.cxx b/src/input/ProxyInputStream.cxx
new file mode 100644
index 000000000..74a272f6a
--- /dev/null
+++ b/src/input/ProxyInputStream.cxx
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ProxyInputStream.hxx"
+#include "tag/Tag.hxx"
+
+#include <assert.h>
+
+ProxyInputStream::ProxyInputStream(InputStream *_input)
+ :InputStream(_input->GetURI(), _input->mutex, _input->cond),
+ input(*_input) {}
+
+ProxyInputStream::~ProxyInputStream()
+{
+ delete &input;
+}
+
+void
+ProxyInputStream::CopyAttributes()
+{
+ if (input.IsReady()) {
+ if (!IsReady()) {
+ if (input.HasMimeType())
+ SetMimeType(input.GetMimeType());
+
+ size = input.KnownSize()
+ ? input.GetSize()
+ : UNKNOWN_SIZE;
+
+ seekable = input.IsSeekable();
+ SetReady();
+ }
+
+ offset = input.GetOffset();
+ }
+}
+
+bool
+ProxyInputStream::Check(Error &error)
+{
+ return input.Check(error);
+}
+
+void
+ProxyInputStream::Update()
+{
+ input.Update();
+ CopyAttributes();
+}
+
+bool
+ProxyInputStream::Seek(offset_type new_offset, Error &error)
+{
+ bool success = input.Seek(new_offset, error);
+ CopyAttributes();
+ return success;
+}
+
+bool
+ProxyInputStream::IsEOF()
+{
+ return input.IsEOF();
+}
+
+Tag *
+ProxyInputStream::ReadTag()
+{
+ return input.ReadTag();
+}
+
+bool
+ProxyInputStream::IsAvailable()
+{
+ return input.IsAvailable();
+}
+
+size_t
+ProxyInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ size_t nbytes = input.Read(ptr, read_size, error);
+ CopyAttributes();
+ return nbytes;
+}
diff --git a/src/input/ProxyInputStream.hxx b/src/input/ProxyInputStream.hxx
new file mode 100644
index 000000000..727ae5917
--- /dev/null
+++ b/src/input/ProxyInputStream.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PROXY_INPUT_STREAM_HXX
+#define MPD_PROXY_INPUT_STREAM_HXX
+
+#include "InputStream.hxx"
+
+struct Tag;
+
+/**
+ * An #InputStream that forwards all methods call to another
+ * #InputStream instance. This can be used as a base class to
+ * override selected methods.
+ */
+class ProxyInputStream : public InputStream {
+protected:
+ InputStream &input;
+
+public:
+ gcc_nonnull_all
+ ProxyInputStream(InputStream *_input);
+
+ virtual ~ProxyInputStream();
+
+ ProxyInputStream(const ProxyInputStream &) = delete;
+ ProxyInputStream &operator=(const ProxyInputStream &) = delete;
+
+ /* virtual methods from InputStream */
+ bool Check(Error &error) override;
+ void Update() override;
+ bool Seek(offset_type new_offset, Error &error) override;
+ bool IsEOF() override;
+ Tag *ReadTag() override;
+ bool IsAvailable() override;
+ size_t Read(void *ptr, size_t read_size, Error &error) override;
+
+protected:
+ /**
+ * 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();
+};
+
+#endif
diff --git a/src/input/Registry.cxx b/src/input/Registry.cxx
new file mode 100644
index 000000000..2b981df1c
--- /dev/null
+++ b/src/input/Registry.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Registry.hxx"
+#include "util/Macros.hxx"
+#include "plugins/FileInputPlugin.hxx"
+
+#ifdef HAVE_ALSA
+#include "plugins/AlsaInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_ARCHIVE
+#include "plugins/ArchiveInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_CURL
+#include "plugins/CurlInputPlugin.hxx"
+#endif
+
+#ifdef HAVE_FFMPEG
+#include "plugins/FfmpegInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_SMBCLIENT
+#include "plugins/SmbclientInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_NFS
+#include "plugins/NfsInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_MMS
+#include "plugins/MmsInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_CDIO_PARANOIA
+#include "plugins/CdioParanoiaInputPlugin.hxx"
+#endif
+
+#ifdef ENABLE_DESPOTIFY
+#include "plugins/DespotifyInputPlugin.hxx"
+#endif
+
+const InputPlugin *const input_plugins[] = {
+ &input_plugin_file,
+#ifdef HAVE_ALSA
+ &input_plugin_alsa,
+#endif
+#ifdef ENABLE_ARCHIVE
+ &input_plugin_archive,
+#endif
+#ifdef ENABLE_CURL
+ &input_plugin_curl,
+#endif
+#ifdef HAVE_FFMPEG
+ &input_plugin_ffmpeg,
+#endif
+#ifdef ENABLE_SMBCLIENT
+ &input_plugin_smbclient,
+#endif
+#ifdef ENABLE_NFS
+ &input_plugin_nfs,
+#endif
+#ifdef ENABLE_MMS
+ &input_plugin_mms,
+#endif
+#ifdef ENABLE_CDIO_PARANOIA
+ &input_plugin_cdio_paranoia,
+#endif
+#ifdef ENABLE_DESPOTIFY
+ &input_plugin_despotify,
+#endif
+ nullptr
+};
+
+bool input_plugins_enabled[ARRAY_SIZE(input_plugins) - 1];
diff --git a/src/input/Registry.hxx b/src/input/Registry.hxx
new file mode 100644
index 000000000..1b81f8f06
--- /dev/null
+++ b/src/input/Registry.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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 InputPlugin *const input_plugins[];
+
+extern bool input_plugins_enabled[];
+
+#define input_plugins_for_each(plugin) \
+ for (const InputPlugin *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/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx
deleted file mode 100644
index e11f56631..000000000
--- a/src/input/RewindInputPlugin.cxx
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RewindInputPlugin.hxx"
-#include "InputStream.hxx"
-#include "InputPlugin.hxx"
-#include "tag/Tag.hxx"
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-
-extern const InputPlugin rewind_input_plugin;
-
-struct RewindInputStream {
- InputStream base;
-
- InputStream *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(InputStream *_input)
- :base(rewind_input_plugin, _input->uri.c_str(),
- _input->mutex, _input->cond),
- input(_input), tail(0) {
- }
-
- ~RewindInputStream() {
- input->Close();
- }
-
- /**
- * 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() {
- InputStream *dest = &base;
- const InputStream *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(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- delete r;
-}
-
-static bool
-input_rewind_check(InputStream *is, Error &error)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return r->input->Check(error);
-}
-
-static void
-input_rewind_update(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- if (!r->ReadingFromBuffer())
- r->CopyAttributes();
-}
-
-static Tag *
-input_rewind_tag(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return r->input->ReadTag();
-}
-
-static bool
-input_rewind_available(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return r->input->IsAvailable();
-}
-
-static size_t
-input_rewind_read(InputStream *is, void *ptr, size_t size,
- Error &error)
-{
- 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 = r->input->Read(ptr, size, error);
-
- if (r->input->offset > (InputPlugin::offset_type)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(InputStream *is)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- return !r->ReadingFromBuffer() && r->input->IsEOF();
-}
-
-static bool
-input_rewind_seek(InputStream *is, InputPlugin::offset_type offset,
- int whence,
- Error &error)
-{
- RewindInputStream *r = (RewindInputStream *)is;
-
- assert(is->ready);
-
- if (whence == SEEK_SET && r->tail > 0 &&
- offset <= (InputPlugin::offset_type)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 = r->input->Seek(offset, whence, error);
- r->CopyAttributes();
-
- /* disable the buffer, because r->input has left the
- buffered range now */
- r->tail = 0;
-
- return success;
- }
-}
-
-const InputPlugin 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,
-};
-
-InputStream *
-input_rewind_open(InputStream *is)
-{
- assert(is != nullptr);
- 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
deleted file mode 100644
index 2d461970a..000000000
--- a/src/input/RewindInputPlugin.hxx
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 InputStream;
-
-InputStream *
-input_rewind_open(InputStream *is);
-
-#endif
diff --git a/src/input/TextInputStream.cxx b/src/input/TextInputStream.cxx
new file mode 100644
index 000000000..5a8dcc065
--- /dev/null
+++ b/src/input/TextInputStream.cxx
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TextInputStream.hxx"
+#include "InputStream.hxx"
+#include "util/Error.hxx"
+#include "util/TextFile.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+char *
+TextInputStream::ReadLine()
+{
+ char *line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+
+ buffer.Shift();
+
+ while (true) {
+ auto dest = buffer.Write();
+ if (dest.size < 2) {
+ /* line too long: terminate the current
+ line */
+
+ assert(!dest.IsEmpty());
+ dest[0] = 0;
+ line = buffer.Read().data;
+ buffer.Clear();
+ return line;
+ }
+
+ /* reserve one byte for the null terminator if the
+ last line is not terminated by a newline
+ character */
+ --dest.size;
+
+ Error error;
+ size_t nbytes = is.LockRead(dest.data, dest.size, error);
+ if (nbytes > 0)
+ buffer.Append(nbytes);
+ else if (error.IsDefined()) {
+ LogError(error);
+ return nullptr;
+ }
+
+ line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+
+ if (nbytes == 0) {
+ /* end of file: see if there's an unterminated
+ line */
+
+ dest = buffer.Write();
+ assert(!dest.IsEmpty());
+ dest[0] = 0;
+
+ auto r = buffer.Read();
+ buffer.Clear();
+ return r.IsEmpty()
+ ? nullptr
+ : r.data;
+ }
+ }
+}
diff --git a/src/input/TextInputStream.hxx b/src/input/TextInputStream.hxx
new file mode 100644
index 000000000..6f39d22cf
--- /dev/null
+++ b/src/input/TextInputStream.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TEXT_INPUT_STREAM_HXX
+#define MPD_TEXT_INPUT_STREAM_HXX
+
+#include "util/StaticFifoBuffer.hxx"
+
+class InputStream;
+
+class TextInputStream {
+ InputStream &is;
+ StaticFifoBuffer<char, 4096> 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(InputStream &_is)
+ :is(_is) {}
+
+ TextInputStream(const TextInputStream &) = delete;
+ TextInputStream& operator=(const TextInputStream &) = delete;
+
+ /**
+ * Reads the next line from the stream with newline character stripped.
+ *
+ * @return a pointer to the line, or nullptr on end-of-file or error
+ */
+ char *ReadLine();
+};
+
+#endif
diff --git a/src/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx
new file mode 100644
index 000000000..235ed2b01
--- /dev/null
+++ b/src/input/ThreadInputStream.cxx
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ThreadInputStream.hxx"
+#include "thread/Name.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/HugeAllocator.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+ThreadInputStream::~ThreadInputStream()
+{
+ Lock();
+ close = true;
+ wake_cond.signal();
+ Unlock();
+
+ Cancel();
+
+ thread.Join();
+
+ if (buffer != nullptr) {
+ buffer->Clear();
+ HugeFree(buffer->Write().data, buffer_size);
+ delete buffer;
+ }
+}
+
+InputStream *
+ThreadInputStream::Start(Error &error)
+{
+ assert(buffer == nullptr);
+
+ void *p = HugeAllocate(buffer_size);
+ if (p == nullptr) {
+ error.SetErrno();
+ return nullptr;
+ }
+
+ buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
+
+ if (!thread.Start(ThreadFunc, this, error))
+ return nullptr;
+
+ return this;
+}
+
+inline void
+ThreadInputStream::ThreadFunc()
+{
+ FormatThreadName("input:%s", plugin);
+
+ Lock();
+ if (!Open(postponed_error)) {
+ cond.broadcast();
+ Unlock();
+ return;
+ }
+
+ /* we're ready, tell it to our client */
+ SetReady();
+
+ while (!close) {
+ assert(!postponed_error.IsDefined());
+
+ auto w = buffer->Write();
+ if (w.IsEmpty()) {
+ wake_cond.wait(mutex);
+ } else {
+ Unlock();
+
+ Error error;
+ size_t nbytes = ThreadRead(w.data, w.size, error);
+
+ Lock();
+ cond.broadcast();
+
+ if (nbytes == 0) {
+ eof = true;
+ postponed_error = std::move(error);
+ break;
+ }
+
+ buffer->Append(nbytes);
+ }
+ }
+
+ Unlock();
+
+ Close();
+}
+
+void
+ThreadInputStream::ThreadFunc(void *ctx)
+{
+ ThreadInputStream &tis = *(ThreadInputStream *)ctx;
+ tis.ThreadFunc();
+}
+
+bool
+ThreadInputStream::Check(Error &error)
+{
+ assert(!thread.IsInside());
+
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ThreadInputStream::IsAvailable()
+{
+ assert(!thread.IsInside());
+
+ return !buffer->IsEmpty() || eof || postponed_error.IsDefined();
+}
+
+inline size_t
+ThreadInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ assert(!thread.IsInside());
+
+ while (true) {
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ return 0;
+ }
+
+ auto r = buffer->Read();
+ if (!r.IsEmpty()) {
+ size_t nbytes = std::min(read_size, r.size);
+ memcpy(ptr, r.data, nbytes);
+ buffer->Consume(nbytes);
+ wake_cond.broadcast();
+ offset += nbytes;
+ return nbytes;
+ }
+
+ if (eof)
+ return 0;
+
+ cond.wait(mutex);
+ }
+}
+
+bool
+ThreadInputStream::IsEOF()
+{
+ assert(!thread.IsInside());
+
+ return eof;
+}
diff --git a/src/input/ThreadInputStream.hxx b/src/input/ThreadInputStream.hxx
new file mode 100644
index 000000000..c6ac7669c
--- /dev/null
+++ b/src/input/ThreadInputStream.hxx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_THREAD_INPUT_STREAM_HXX
+#define MPD_THREAD_INPUT_STREAM_HXX
+
+#include "check.h"
+#include "InputStream.hxx"
+#include "thread/Thread.hxx"
+#include "thread/Cond.hxx"
+#include "util/Error.hxx"
+
+#include <stdint.h>
+
+template<typename T> class CircularBuffer;
+
+/**
+ * Helper class for moving InputStream implementations with blocking
+ * backend library implementation to a dedicated thread. Data is
+ * being read into a ring buffer, and that buffer is then consumed by
+ * another thread using the regular #InputStream API. This class
+ * manages the thread and the buffer.
+ *
+ * This works only for "streams": unknown length, no seeking, no tags.
+ */
+class ThreadInputStream : public InputStream {
+ const char *const plugin;
+
+ Thread thread;
+
+ /**
+ * Signalled when the thread shall be woken up: when data from
+ * the buffer has been consumed and when the stream shall be
+ * closed.
+ */
+ Cond wake_cond;
+
+ Error postponed_error;
+
+ const size_t buffer_size;
+ CircularBuffer<uint8_t> *buffer;
+
+ /**
+ * Shall the stream be closed?
+ */
+ bool close;
+
+ /**
+ * Has the end of the stream been seen by the thread?
+ */
+ bool eof;
+
+public:
+ ThreadInputStream(const char *_plugin,
+ const char *_uri, Mutex &_mutex, Cond &_cond,
+ size_t _buffer_size)
+ :InputStream(_uri, _mutex, _cond),
+ plugin(_plugin),
+ buffer_size(_buffer_size),
+ buffer(nullptr),
+ close(false), eof(false) {}
+
+ virtual ~ThreadInputStream();
+
+ /**
+ * Initialize the object and start the thread.
+ *
+ * @return false on error
+ */
+ InputStream *Start(Error &error);
+
+ /* virtual methods from InputStream */
+ bool Check(Error &error) override final;
+ bool IsEOF() override final;
+ bool IsAvailable() override final;
+ size_t Read(void *ptr, size_t size, Error &error) override final;
+
+protected:
+ void SetMimeType(const char *_mime) {
+ assert(thread.IsInside());
+
+ InputStream::SetMimeType(_mime);
+ }
+
+ /* to be implemented by the plugin */
+
+ /**
+ * Optional initialization after entering the thread. After
+ * this returns with success, the InputStream::ready flag is
+ * set.
+ *
+ * The #InputStream is locked. Unlock/relock it if you do a
+ * blocking operation.
+ */
+ virtual bool Open(gcc_unused Error &error) {
+ return true;
+ }
+
+ /**
+ * Read from the stream.
+ *
+ * The #InputStream is not locked.
+ *
+ * @return 0 on end-of-file or on error
+ */
+ virtual size_t ThreadRead(void *ptr, size_t size, Error &error) = 0;
+
+ /**
+ * Optional deinitialization before leaving the thread.
+ *
+ * The #InputStream is not locked.
+ */
+ virtual void Close() {}
+
+ /**
+ * Called from the client thread to cancel a Read() inside the
+ * thread.
+ *
+ * The #InputStream is not locked.
+ */
+ virtual void Cancel() {}
+
+private:
+ void ThreadFunc();
+ static void ThreadFunc(void *ctx);
+};
+
+#endif
diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx
new file mode 100644
index 000000000..f03f745c6
--- /dev/null
+++ b/src/input/plugins/AlsaInputPlugin.cxx
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * ALSA code based on an example by Paul Davis released under GPL here:
+ * http://equalarea.com/paul/alsa-audio.html
+ * and one by Matthias Nagorni, also GPL, here:
+ * http://alsamodular.sourceforge.net/alsa_programming_howto.html
+ */
+
+#include "config.h"
+#include "AlsaInputPlugin.hxx"
+#include "../InputPlugin.hxx"
+#include "../InputStream.hxx"
+#include "util/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/StringUtil.hxx"
+#include "util/ReusableArray.hxx"
+
+#include "Log.hxx"
+#include "event/MultiSocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "event/Call.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "IOThread.hxx"
+
+#include <alsa/asoundlib.h>
+
+#include <atomic>
+
+#include <assert.h>
+#include <string.h>
+
+static constexpr Domain alsa_input_domain("alsa");
+
+static constexpr const char *default_device = "hw:0,0";
+
+// the following defaults are because the PcmDecoderPlugin forces CD format
+static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
+static constexpr int default_channels = 2; // stereo
+static constexpr unsigned int default_rate = 44100; // cd quality
+
+/**
+ * This value should be the same as the read buffer size defined in
+ * PcmDecoderPlugin.cxx:pcm_stream_decode().
+ * We use it to calculate how many audio frames to buffer in the alsa driver
+ * before reading from the device. snd_pcm_readi() blocks until that many
+ * frames are ready.
+ */
+static constexpr size_t read_buffer_size = 4096;
+
+class AlsaInputStream final
+ : public InputStream,
+ MultiSocketMonitor, DeferredMonitor {
+ snd_pcm_t *capture_handle;
+ size_t frame_size;
+ int frames_to_read;
+ bool eof;
+
+ /**
+ * Is somebody waiting for data? This is set by method
+ * Available().
+ */
+ std::atomic_bool waiting;
+
+ ReusableArray<pollfd> pfd_buffer;
+
+public:
+ AlsaInputStream(EventLoop &loop,
+ const char *_uri, Mutex &_mutex, Cond &_cond,
+ snd_pcm_t *_handle, int _frame_size)
+ :InputStream(_uri, _mutex, _cond),
+ MultiSocketMonitor(loop),
+ DeferredMonitor(loop),
+ capture_handle(_handle),
+ frame_size(_frame_size),
+ eof(false)
+ {
+ assert(_uri != nullptr);
+ assert(_handle != nullptr);
+
+ /* this mime type forces use of the PcmDecoderPlugin.
+ Needs to be generalised when/if that decoder is
+ updated to support other audio formats */
+ SetMimeType("audio/x-mpd-cdda-pcm");
+ InputStream::SetReady();
+
+ frames_to_read = read_buffer_size / frame_size;
+
+ snd_pcm_start(capture_handle);
+
+ DeferredMonitor::Schedule();
+ }
+
+ ~AlsaInputStream() {
+ snd_pcm_close(capture_handle);
+ }
+
+ using DeferredMonitor::GetEventLoop;
+
+ static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return eof;
+ }
+
+ bool IsAvailable() override {
+ if (snd_pcm_avail(capture_handle) > frames_to_read)
+ return true;
+
+ if (!waiting.exchange(true))
+ SafeInvalidateSockets();
+
+ return false;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+
+private:
+ static snd_pcm_t *OpenDevice(const char *device, int rate,
+ snd_pcm_format_t format, int channels,
+ Error &error);
+
+ int Recover(int err);
+
+ void SafeInvalidateSockets() {
+ DeferredMonitor::Schedule();
+ }
+
+ virtual void RunDeferred() override {
+ InvalidateSockets();
+ }
+
+ virtual int PrepareSockets() override;
+ virtual void DispatchSockets() override;
+};
+
+inline InputStream *
+AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ const char *const scheme = "alsa://";
+ if (!StringStartsWith(uri, scheme))
+ return nullptr;
+
+ const char *device = uri + strlen(scheme);
+ if (strlen(device) == 0)
+ device = default_device;
+
+ /* placeholders - eventually user-requested audio format will
+ be passed via the URI. For now we just force the
+ defaults */
+ int rate = default_rate;
+ snd_pcm_format_t format = default_format;
+ int channels = default_channels;
+
+ snd_pcm_t *handle = OpenDevice(device, rate, format, channels,
+ error);
+ if (handle == nullptr)
+ return nullptr;
+
+ int frame_size = snd_pcm_format_width(format) / 8 * channels;
+ return new AlsaInputStream(io_thread_get(),
+ uri, mutex, cond,
+ handle, frame_size);
+}
+
+size_t
+AlsaInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ assert(ptr != nullptr);
+
+ int num_frames = read_size / frame_size;
+ int ret;
+ while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) {
+ if (Recover(ret) < 0) {
+ eof = true;
+ error.Format(alsa_input_domain,
+ "PCM error - stream aborted");
+ return 0;
+ }
+ }
+
+ size_t nbytes = ret * frame_size;
+ offset += nbytes;
+ return nbytes;
+}
+
+int
+AlsaInputStream::PrepareSockets()
+{
+ if (!waiting) {
+ ClearSocketList();
+ return -1;
+ }
+
+ int count = snd_pcm_poll_descriptors_count(capture_handle);
+ if (count < 0) {
+ ClearSocketList();
+ return -1;
+ }
+
+ struct pollfd *pfds = pfd_buffer.Get(count);
+
+ count = snd_pcm_poll_descriptors(capture_handle, pfds, count);
+ if (count < 0)
+ count = 0;
+
+ ReplaceSocketList(pfds, count);
+ return -1;
+}
+
+void
+AlsaInputStream::DispatchSockets()
+{
+ waiting = false;
+
+ const ScopeLock protect(mutex);
+ /* wake up the thread that is waiting for more data */
+ cond.broadcast();
+}
+
+inline int
+AlsaInputStream::Recover(int err)
+{
+ switch(err) {
+ case -EPIPE:
+ LogDebug(alsa_input_domain, "Buffer Overrun");
+ // drop through
+ case -ESTRPIPE:
+ case -EINTR:
+ err = snd_pcm_recover(capture_handle, err, 1);
+ break;
+ default:
+ // something broken somewhere, give up
+ err = -1;
+ }
+ return err;
+}
+
+inline snd_pcm_t *
+AlsaInputStream::OpenDevice(const char *device,
+ int rate, snd_pcm_format_t format, int channels,
+ Error &error)
+{
+ snd_pcm_t *capture_handle;
+ int err;
+ if ((err = snd_pcm_open(&capture_handle, device,
+ SND_PCM_STREAM_CAPTURE, 0)) < 0) {
+ error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err));
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_t *hw_params;
+ if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ /* period needs to be big enough so that poll() doesn't fire too often,
+ * but small enough that buffer overruns don't occur if Read() is not
+ * invoked often enough.
+ * the calculation here is empirical; however all measurements were
+ * done using 44100:16:2. When we extend this plugin to support
+ * other audio formats then this may need to be revisited */
+ snd_pcm_uframes_t period = read_buffer_size * 2;
+ int direction = -1;
+ if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params,
+ &period, &direction)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set period size (%s)",
+ snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set parameters (%s)",
+ snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_free (hw_params);
+
+ snd_pcm_sw_params_t *sw_params;
+
+ snd_pcm_sw_params_malloc(&sw_params);
+ snd_pcm_sw_params_current(capture_handle, sw_params);
+
+ if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params,
+ period)) < 0) {
+ error.Format(alsa_input_domain,
+ "unable to set start threshold (%s)", snd_strerror(err));
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) {
+ error.Format(alsa_input_domain,
+ "unable to install sw params (%s)", snd_strerror(err));
+ snd_pcm_sw_params_free(sw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_sw_params_free(sw_params);
+
+ snd_pcm_prepare(capture_handle);
+
+ return capture_handle;
+}
+
+/*######################### Plugin Functions ##############################*/
+
+static InputStream *
+alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error)
+{
+ return AlsaInputStream::Create(uri, mutex, cond, error);
+}
+
+const struct InputPlugin input_plugin_alsa = {
+ "alsa",
+ nullptr,
+ nullptr,
+ alsa_input_open,
+};
diff --git a/src/input/plugins/AlsaInputPlugin.hxx b/src/input/plugins/AlsaInputPlugin.hxx
new file mode 100644
index 000000000..dddf7dfd7
--- /dev/null
+++ b/src/input/plugins/AlsaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ALSA_INPUT_PLUGIN_HXX
+#define MPD_ALSA_INPUT_PLUGIN_HXX
+
+#include "../InputPlugin.hxx"
+
+extern const struct InputPlugin input_plugin_alsa;
+
+
+#endif
diff --git a/src/input/plugins/ArchiveInputPlugin.cxx b/src/input/plugins/ArchiveInputPlugin.cxx
new file mode 100644
index 000000000..da3d7ca71
--- /dev/null
+++ b/src/input/plugins/ArchiveInputPlugin.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ArchiveInputPlugin.hxx"
+#include "archive/ArchiveDomain.hxx"
+#include "archive/ArchiveLookup.hxx"
+#include "archive/ArchiveList.hxx"
+#include "archive/ArchivePlugin.hxx"
+#include "archive/ArchiveFile.hxx"
+#include "../InputPlugin.hxx"
+#include "fs/Traits.hxx"
+#include "fs/Path.hxx"
+#include "util/Alloc.hxx"
+#include "Log.hxx"
+
+#include <stdlib.h>
+
+InputStream *
+OpenArchiveInputStream(Path path, Mutex &mutex, Cond &cond, Error &error)
+{
+ const ArchivePlugin *arplug;
+ InputStream *is;
+
+ char *pname = strdup(path.c_str());
+ // archive_lookup will modify pname when true is returned
+ const char *archive, *filename, *suffix;
+ if (!archive_lookup(pname, &archive, &filename, &suffix)) {
+ FormatDebug(archive_domain,
+ "not an archive, lookup %s failed", pname);
+ free(pname);
+ return nullptr;
+ }
+
+ //check which archive plugin to use (by ext)
+ arplug = archive_plugin_from_suffix(suffix);
+ if (!arplug) {
+ FormatWarning(archive_domain,
+ "can't handle archive %s", archive);
+ free(pname);
+ return nullptr;
+ }
+
+ auto file = archive_file_open(arplug, Path::FromFS(archive), error);
+ if (file == nullptr) {
+ free(pname);
+ return nullptr;
+ }
+
+ //setup fileops
+ is = file->OpenStream(filename, mutex, cond, error);
+ free(pname);
+ file->Close();
+
+ return is;
+}
+
+static InputStream *
+input_archive_open(gcc_unused const char *filename,
+ gcc_unused Mutex &mutex, gcc_unused Cond &cond,
+ gcc_unused Error &error)
+{
+ /* dummy method; use OpenArchiveInputStream() instead */
+
+ return nullptr;
+}
+
+const InputPlugin input_plugin_archive = {
+ "archive",
+ nullptr,
+ nullptr,
+ input_archive_open,
+};
diff --git a/src/input/plugins/ArchiveInputPlugin.hxx b/src/input/plugins/ArchiveInputPlugin.hxx
new file mode 100644
index 000000000..b6158684a
--- /dev/null
+++ b/src/input/plugins/ArchiveInputPlugin.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_ARCHIVE_HXX
+#define MPD_INPUT_ARCHIVE_HXX
+
+class InputStream;
+class Path;
+class Mutex;
+class Cond;
+class Error;
+
+extern const struct InputPlugin input_plugin_archive;
+
+InputStream *
+OpenArchiveInputStream(Path path, Mutex &mutex, Cond &cond, Error &error);
+
+#endif
diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx
new file mode 100644
index 000000000..f847b35c1
--- /dev/null
+++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * CD-Audio handling (requires libcdio_paranoia)
+ */
+
+#include "config.h"
+#include "CdioParanoiaInputPlugin.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Log.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <assert.h>
+
+#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
+#include <cdio/paranoia/paranoia.h>
+#else
+#include <cdio/paranoia.h>
+#endif
+
+#include <cdio/cd_types.h>
+
+class CdioParanoiaInputStream final : public InputStream {
+ cdrom_drive_t *const drv;
+ CdIo_t *const cdio;
+ cdrom_paranoia_t *const para;
+
+ const lsn_t lsn_from, lsn_to;
+ int lsn_relofs;
+
+ char buffer[CDIO_CD_FRAMESIZE_RAW];
+ int buffer_lsn;
+
+ public:
+ CdioParanoiaInputStream(const char *_uri, Mutex &_mutex, Cond &_cond,
+ cdrom_drive_t *_drv, CdIo_t *_cdio,
+ bool reverse_endian,
+ lsn_t _lsn_from, lsn_t _lsn_to)
+ :InputStream(_uri, _mutex, _cond),
+ drv(_drv), cdio(_cdio), para(cdio_paranoia_init(drv)),
+ lsn_from(_lsn_from), lsn_to(_lsn_to),
+ lsn_relofs(0),
+ buffer_lsn(-1)
+ {
+ /* Set reading mode for full paranoia, but allow
+ skipping sectors. */
+ paranoia_modeset(para,
+ PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
+
+ /* seek to beginning of the track */
+ cdio_paranoia_seek(para, lsn_from, SEEK_SET);
+
+ seekable = true;
+ size = (lsn_to - lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
+
+ /* hack to make MPD select the "pcm" decoder plugin */
+ SetMimeType(reverse_endian
+ ? "audio/x-mpd-cdda-pcm-reverse"
+ : "audio/x-mpd-cdda-pcm");
+ SetReady();
+ }
+
+ ~CdioParanoiaInputStream() {
+ cdio_paranoia_free(para);
+ cdio_cddap_close_no_free_cdio(drv);
+ cdio_destroy(cdio);
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+static constexpr Domain cdio_domain("cdio");
+
+static bool default_reverse_endian;
+
+static InputPlugin::InitResult
+input_cdio_init(const config_param &param, Error &error)
+{
+ const char *value = param.GetBlockValue("default_byte_order");
+ if (value != nullptr) {
+ if (strcmp(value, "little_endian") == 0)
+ default_reverse_endian = IsBigEndian();
+ else if (strcmp(value, "big_endian") == 0)
+ default_reverse_endian = IsLittleEndian();
+ else {
+ error.Format(config_domain, 0,
+ "Unrecognized 'default_byte_order' setting: %s",
+ value);
+ return InputPlugin::InitResult::ERROR;
+ }
+ }
+
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+struct cdio_uri {
+ char device[64];
+ int track;
+};
+
+static bool
+parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error)
+{
+ if (!StringStartsWith(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) {
+ error.Set(cdio_domain, "Malformed track number");
+ return false;
+ }
+
+ if (endptr == track)
+ /* play the whole CD */
+ dest->track = -1;
+
+ return true;
+}
+
+static AllocatedPath
+cdio_detect_device(void)
+{
+ char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO,
+ false);
+ if (devices == nullptr)
+ return AllocatedPath::Null();
+
+ AllocatedPath path = AllocatedPath::FromFS(devices[0]);
+ cdio_free_device_list(devices);
+ return path;
+}
+
+static InputStream *
+input_cdio_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ struct cdio_uri parsed_uri;
+ if (!parse_cdio_uri(&parsed_uri, uri, error))
+ return nullptr;
+
+ /* get list of CD's supporting CD-DA */
+ const AllocatedPath device = parsed_uri.device[0] != 0
+ ? AllocatedPath::FromFS(parsed_uri.device)
+ : cdio_detect_device();
+ if (device.IsNull()) {
+ error.Set(cdio_domain,
+ "Unable find or access a CD-ROM drive with an audio CD in it.");
+ return nullptr;
+ }
+
+ /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */
+ const auto cdio = cdio_open(device.c_str(), DRIVER_UNKNOWN);
+ if (cdio == nullptr) {
+ error.Set(cdio_domain, "Failed to open CD drive");
+ return nullptr;
+ }
+
+ const auto drv = cdio_cddap_identify_cdio(cdio, 1, nullptr);
+ if (drv == nullptr) {
+ error.Set(cdio_domain, "Unable to identify audio CD disc.");
+ cdio_destroy(cdio);
+ return nullptr;
+ }
+
+ cdda_verbose_set(drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
+
+ if (0 != cdio_cddap_open(drv)) {
+ cdio_cddap_close_no_free_cdio(drv);
+ cdio_destroy(cdio);
+ error.Set(cdio_domain, "Unable to open disc.");
+ return nullptr;
+ }
+
+ bool reverse_endian;
+ switch (data_bigendianp(drv)) {
+ case -1:
+ LogDebug(cdio_domain, "drive returns unknown audio data");
+ reverse_endian = default_reverse_endian;
+ break;
+
+ case 0:
+ LogDebug(cdio_domain, "drive returns audio data Little Endian");
+ reverse_endian = IsBigEndian();
+ break;
+
+ case 1:
+ LogDebug(cdio_domain, "drive returns audio data Big Endian");
+ reverse_endian = IsLittleEndian();
+ break;
+
+ default:
+ error.Format(cdio_domain, "Drive returns unknown data type %d",
+ data_bigendianp(drv));
+ cdio_cddap_close_no_free_cdio(drv);
+ cdio_destroy(cdio);
+ return nullptr;
+ }
+
+ lsn_t lsn_from, lsn_to;
+ if (parsed_uri.track >= 0) {
+ lsn_from = cdio_get_track_lsn(cdio, parsed_uri.track);
+ lsn_to = cdio_get_track_last_lsn(cdio, parsed_uri.track);
+ } else {
+ lsn_from = 0;
+ lsn_to = cdio_get_disc_last_lsn(cdio);
+ }
+
+ return new CdioParanoiaInputStream(uri, mutex, cond,
+ drv, cdio, reverse_endian,
+ lsn_from, lsn_to);
+}
+
+bool
+CdioParanoiaInputStream::Seek(offset_type new_offset, Error &error)
+{
+ if (new_offset > size) {
+ error.Format(cdio_domain, "Invalid offset to seek %ld (%ld)",
+ (long int)new_offset, (long int)size);
+ return false;
+ }
+
+ /* simple case */
+ if (new_offset == offset)
+ return true;
+
+ /* calculate current LSN */
+ lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
+ offset = new_offset;
+
+ cdio_paranoia_seek(para, lsn_from + lsn_relofs, SEEK_SET);
+
+ return true;
+}
+
+size_t
+CdioParanoiaInputStream::Read(void *ptr, size_t length, Error &error)
+{
+ 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 (lsn_from + lsn_relofs > lsn_to)
+ break;
+
+ //current sector was changed ?
+ if (lsn_relofs != buffer_lsn) {
+ rbuf = cdio_paranoia_read(para, nullptr);
+
+ s_err = cdda_errors(drv);
+ if (s_err) {
+ FormatError(cdio_domain,
+ "paranoia_read: %s", s_err);
+ free(s_err);
+ }
+ s_mess = cdda_messages(drv);
+ if (s_mess) {
+ free(s_mess);
+ }
+ if (!rbuf) {
+ error.Set(cdio_domain,
+ "paranoia read error. Stopping.");
+ return 0;
+ }
+ //store current buffer
+ memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
+ buffer_lsn = lsn_relofs;
+ } else {
+ //use cached sector
+ rbuf = (int16_t *)buffer;
+ }
+
+ //correct offset
+ diff = offset - 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
+ offset += len;
+ lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
+ //update length
+ length -= len;
+ }
+
+ return nbytes;
+}
+
+bool
+CdioParanoiaInputStream::IsEOF()
+{
+ return lsn_from + lsn_relofs > lsn_to;
+}
+
+const InputPlugin input_plugin_cdio_paranoia = {
+ "cdio_paranoia",
+ input_cdio_init,
+ nullptr,
+ input_cdio_open,
+};
diff --git a/src/input/plugins/CdioParanoiaInputPlugin.hxx b/src/input/plugins/CdioParanoiaInputPlugin.hxx
new file mode 100644
index 000000000..e2804e8c7
--- /dev/null
+++ b/src/input/plugins/CdioParanoiaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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 InputPlugin input_plugin_cdio_paranoia;
+
+#endif
diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
new file mode 100644
index 000000000..abb7e312c
--- /dev/null
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -0,0 +1,876 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CurlInputPlugin.hxx"
+#include "../AsyncInputStream.hxx"
+#include "../IcyInputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/TimeoutMonitor.hxx"
+#include "event/Call.hxx"
+#include "IOThread.hxx"
+#include "util/ASCII.hxx"
+#include "util/StringUtil.hxx"
+#include "util/NumberParser.hxx"
+#include "util/CircularBuffer.hxx"
+#include "util/HugeAllocator.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#include <curl/curl.h>
+
+#if LIBCURL_VERSION_NUM < 0x071200
+#error libcurl is too old
+#endif
+
+/**
+ * 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;
+
+struct CurlInputStream final : public AsyncInputStream {
+ /* some buffers which were passed to libcurl, which we have
+ too free */
+ char range[32];
+ struct curl_slist *request_headers;
+
+ /** the curl handles */
+ CURL *easy;
+
+ /** error message provided by libcurl */
+ char error_buffer[CURL_ERROR_SIZE];
+
+ /** parser for icy-metadata */
+ IcyInputStream *icy;
+
+ CurlInputStream(const char *_url, Mutex &_mutex, Cond &_cond,
+ void *_buffer)
+ :AsyncInputStream(_url, _mutex, _cond,
+ _buffer, CURL_MAX_BUFFERED,
+ CURL_RESUME_AT),
+ request_headers(nullptr),
+ icy(new IcyInputStream(this)) {}
+
+ ~CurlInputStream();
+
+ CurlInputStream(const CurlInputStream &) = delete;
+ CurlInputStream &operator=(const CurlInputStream &) = delete;
+
+ static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ bool InitEasy(Error &error);
+
+ /**
+ * Frees the current "libcurl easy" handle, and everything
+ * associated with it.
+ *
+ * Runs in the I/O thread.
+ */
+ void FreeEasy();
+
+ /**
+ * Frees the current "libcurl easy" handle, and everything associated
+ * with it.
+ *
+ * The mutex must not be locked.
+ */
+ void FreeEasyIndirect();
+
+ /**
+ * Called when a new response begins. This is used to discard
+ * headers from previous responses (for example authentication
+ * and redirects).
+ */
+ void ResponseBoundary();
+
+ void HeaderReceived(const char *name, std::string &&value);
+
+ size_t DataReceived(const void *ptr, size_t size);
+
+ /**
+ * A HTTP request is finished.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+ void RequestDone(CURLcode result, long status);
+
+ /* virtual methods from AsyncInputStream */
+ virtual void DoResume() override;
+ virtual void DoSeek(offset_type new_offset) override;
+};
+
+class CurlMulti;
+
+/**
+ * Monitor for one socket created by CURL.
+ */
+class CurlSocket final : SocketMonitor {
+ CurlMulti &multi;
+
+public:
+ CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd)
+ :SocketMonitor(_fd, _loop), multi(_multi) {}
+
+ ~CurlSocket() {
+ /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after
+ closing the socket, and sometimes, it uses
+ CURL_POLL_REMOVE just to move the (still open)
+ connection to the pool; in the first case,
+ Abandon() would be most appropriate, but it breaks
+ the second case - is that a CURL bug? is there a
+ better solution? */
+ }
+
+ /**
+ * Callback function for CURLMOPT_SOCKETFUNCTION.
+ */
+ static int SocketFunction(CURL *easy,
+ curl_socket_t s, int action,
+ void *userp, void *socketp);
+
+ virtual bool OnSocketReady(unsigned flags) override;
+
+private:
+ static constexpr int FlagsToCurlCSelect(unsigned flags) {
+ return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) |
+ (flags & WRITE ? CURL_CSELECT_OUT : 0) |
+ (flags & ERROR ? CURL_CSELECT_ERR : 0);
+ }
+
+ gcc_const
+ static unsigned CurlPollToFlags(int action) {
+ switch (action) {
+ case CURL_POLL_NONE:
+ return 0;
+
+ case CURL_POLL_IN:
+ return READ;
+
+ case CURL_POLL_OUT:
+ return WRITE;
+
+ case CURL_POLL_INOUT:
+ return READ|WRITE;
+ }
+
+ assert(false);
+ gcc_unreachable();
+ }
+};
+
+/**
+ * Manager for the global CURLM object.
+ */
+class CurlMulti final : private TimeoutMonitor {
+ CURLM *const multi;
+
+public:
+ CurlMulti(EventLoop &_loop, CURLM *_multi);
+
+ ~CurlMulti() {
+ curl_multi_cleanup(multi);
+ }
+
+ bool Add(CurlInputStream *c, Error &error);
+ void Remove(CurlInputStream *c);
+
+ /**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+ void ReadInfo();
+
+ void Assign(curl_socket_t fd, CurlSocket &cs) {
+ curl_multi_assign(multi, fd, &cs);
+ }
+
+ void SocketAction(curl_socket_t fd, int ev_bitmask);
+
+ void InvalidateSockets() {
+ SocketAction(CURL_SOCKET_TIMEOUT, 0);
+ }
+
+ /**
+ * This is a kludge to allow pausing/resuming a stream with
+ * libcurl < 7.32.0. Read the curl_easy_pause manpage for
+ * more information.
+ */
+ void ResumeSockets() {
+ int running_handles;
+ curl_multi_socket_all(multi, &running_handles);
+ }
+
+private:
+ static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
+
+ virtual void OnTimeout() override;
+};
+
+/**
+ * libcurl version number encoded in a 24 bit integer.
+ */
+static unsigned curl_version_num;
+
+/** 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 bool verify_peer, verify_host;
+
+static CurlMulti *curl_multi;
+
+static constexpr Domain http_domain("http");
+static constexpr Domain curl_domain("curl");
+static constexpr Domain curlm_domain("curlm");
+
+CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi)
+ :TimeoutMonitor(_loop), multi(_multi)
+{
+ curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION,
+ CurlSocket::SocketFunction);
+ curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this);
+
+ curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction);
+ curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this);
+}
+
+/**
+ * Find a request by its CURL "easy" handle.
+ *
+ * Runs in the I/O thread. No lock needed.
+ */
+gcc_pure
+static CurlInputStream *
+input_curl_find_request(CURL *easy)
+{
+ assert(io_thread_inside());
+
+ void *p;
+ CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p);
+ if (code != CURLE_OK)
+ return nullptr;
+
+ return (CurlInputStream *)p;
+}
+
+void
+CurlInputStream::DoResume()
+{
+ assert(io_thread_inside());
+
+ mutex.unlock();
+
+ curl_easy_pause(easy, CURLPAUSE_CONT);
+
+ if (curl_version_num < 0x072000)
+ /* libcurl older than 7.32.0 does not update
+ its sockets after curl_easy_pause(); force
+ libcurl to do it now */
+ curl_multi->ResumeSockets();
+
+ curl_multi->InvalidateSockets();
+
+ mutex.lock();
+}
+
+int
+CurlSocket::SocketFunction(gcc_unused CURL *easy,
+ curl_socket_t s, int action,
+ void *userp, void *socketp) {
+ CurlMulti &multi = *(CurlMulti *)userp;
+ CurlSocket *cs = (CurlSocket *)socketp;
+
+ assert(io_thread_inside());
+
+ if (action == CURL_POLL_REMOVE) {
+ delete cs;
+ return 0;
+ }
+
+ if (cs == nullptr) {
+ cs = new CurlSocket(multi, io_thread_get(), s);
+ multi.Assign(s, *cs);
+ } else {
+#ifdef USE_EPOLL
+ /* when using epoll, we need to unregister the socket
+ each time this callback is invoked, because older
+ CURL versions may omit the CURL_POLL_REMOVE call
+ when the socket has been closed and recreated with
+ the same file number (bug found in CURL 7.26, CURL
+ 7.33 not affected); in that case, epoll refuses the
+ EPOLL_CTL_MOD because it does not know the new
+ socket yet */
+ cs->Cancel();
+#endif
+ }
+
+ unsigned flags = CurlPollToFlags(action);
+ if (flags != 0)
+ cs->Schedule(flags);
+ return 0;
+}
+
+bool
+CurlSocket::OnSocketReady(unsigned flags)
+{
+ assert(io_thread_inside());
+
+ multi.SocketAction(Get(), FlagsToCurlCSelect(flags));
+ return true;
+}
+
+/**
+ * Runs in the I/O thread. No lock needed.
+ */
+inline bool
+CurlMulti::Add(CurlInputStream *c, Error &error)
+{
+ assert(io_thread_inside());
+ assert(c != nullptr);
+ assert(c->easy != nullptr);
+
+ CURLMcode mcode = curl_multi_add_handle(multi, c->easy);
+ if (mcode != CURLM_OK) {
+ error.Format(curlm_domain, mcode,
+ "curl_multi_add_handle() failed: %s",
+ curl_multi_strerror(mcode));
+ return false;
+ }
+
+ InvalidateSockets();
+ return true;
+}
+
+/**
+ * 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(CurlInputStream *c, Error &error)
+{
+ assert(c != nullptr);
+ assert(c->easy != nullptr);
+
+ bool result;
+ BlockingCall(io_thread_get(), [c, &error, &result](){
+ result = curl_multi->Add(c, error);
+ });
+ return result;
+}
+
+inline void
+CurlMulti::Remove(CurlInputStream *c)
+{
+ curl_multi_remove_handle(multi, c->easy);
+}
+
+void
+CurlInputStream::FreeEasy()
+{
+ assert(io_thread_inside());
+
+ if (easy == nullptr)
+ return;
+
+ curl_multi->Remove(this);
+
+ curl_easy_cleanup(easy);
+ easy = nullptr;
+
+ curl_slist_free_all(request_headers);
+ request_headers = nullptr;
+}
+
+void
+CurlInputStream::FreeEasyIndirect()
+{
+ BlockingCall(io_thread_get(), [this](){
+ FreeEasy();
+ curl_multi->InvalidateSockets();
+ });
+
+ assert(easy == nullptr);
+}
+
+inline void
+CurlInputStream::RequestDone(CURLcode result, long status)
+{
+ assert(io_thread_inside());
+ assert(!postponed_error.IsDefined());
+
+ FreeEasy();
+ AsyncInputStream::SetClosed();
+
+ const ScopeLock protect(mutex);
+
+ if (result != CURLE_OK) {
+ postponed_error.Format(curl_domain, result,
+ "curl failed: %s", error_buffer);
+ } else if (status < 200 || status >= 300) {
+ postponed_error.Format(http_domain, status,
+ "got HTTP status %ld",
+ status);
+ }
+
+ if (IsSeekPending())
+ SeekDone();
+ else if (!IsReady())
+ SetReady();
+}
+
+static void
+input_curl_handle_done(CURL *easy_handle, CURLcode result)
+{
+ CurlInputStream *c = input_curl_find_request(easy_handle);
+ assert(c != nullptr);
+
+ long status = 0;
+ curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);
+
+ c->RequestDone(result, status);
+}
+
+void
+CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask)
+{
+ int running_handles;
+ CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask,
+ &running_handles);
+ if (mcode != CURLM_OK)
+ FormatError(curlm_domain,
+ "curl_multi_socket_action() failed: %s",
+ curl_multi_strerror(mcode));
+
+ ReadInfo();
+}
+
+/**
+ * Check for finished HTTP responses.
+ *
+ * Runs in the I/O thread. The caller must not hold locks.
+ */
+inline void
+CurlMulti::ReadInfo()
+{
+ assert(io_thread_inside());
+
+ CURLMsg *msg;
+ int msgs_in_queue;
+
+ while ((msg = curl_multi_info_read(multi,
+ &msgs_in_queue)) != nullptr) {
+ if (msg->msg == CURLMSG_DONE)
+ input_curl_handle_done(msg->easy_handle, msg->data.result);
+ }
+}
+
+int
+CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp)
+{
+ CurlMulti &multi = *(CurlMulti *)userp;
+ assert(_multi == multi.multi);
+
+ if (timeout_ms < 0) {
+ multi.Cancel();
+ return 0;
+ }
+
+ if (timeout_ms >= 0 && timeout_ms < 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. */
+ timeout_ms = 10;
+
+ multi.Schedule(timeout_ms);
+ return 0;
+}
+
+void
+CurlMulti::OnTimeout()
+{
+ SocketAction(CURL_SOCKET_TIMEOUT, 0);
+}
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputPlugin::InitResult
+input_curl_init(const config_param &param, Error &error)
+{
+ CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+ if (code != CURLE_OK) {
+ error.Format(curl_domain, code,
+ "curl_global_init() failed: %s",
+ curl_easy_strerror(code));
+ return InputPlugin::InitResult::UNAVAILABLE;
+ }
+
+ const auto version_info = curl_version_info(CURLVERSION_FIRST);
+ if (version_info != nullptr) {
+ FormatDebug(curl_domain, "version %s", version_info->version);
+ if (version_info->features & CURL_VERSION_SSL)
+ FormatDebug(curl_domain, "with %s",
+ version_info->ssl_version);
+
+ curl_version_num = version_info->version_num;
+ }
+
+ 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 == nullptr) {
+ /* deprecated proxy configuration */
+ proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr);
+ proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
+ proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr);
+ proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
+ "");
+ }
+
+ verify_peer = param.GetBlockValue("verify_peer", true);
+ verify_host = param.GetBlockValue("verify_host", true);
+
+ CURLM *multi = curl_multi_init();
+ if (multi == nullptr) {
+ curl_slist_free_all(http_200_aliases);
+ curl_global_cleanup();
+ error.Set(curl_domain, 0, "curl_multi_init() failed");
+ return InputPlugin::InitResult::UNAVAILABLE;
+ }
+
+ curl_multi = new CurlMulti(io_thread_get(), multi);
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static void
+input_curl_finish(void)
+{
+ BlockingCall(io_thread_get(), [](){
+ delete curl_multi;
+ });
+
+ curl_slist_free_all(http_200_aliases);
+ http_200_aliases = nullptr;
+
+ curl_global_cleanup();
+}
+
+CurlInputStream::~CurlInputStream()
+{
+ FreeEasyIndirect();
+}
+
+inline void
+CurlInputStream::ResponseBoundary()
+{
+ /* undo all effects of HeaderReceived() because the previous
+ response was not applicable for this stream */
+
+ if (IsSeekPending())
+ /* don't update metadata while seeking */
+ return;
+
+ seekable = false;
+ size = UNKNOWN_SIZE;
+ ClearMimeType();
+ ClearTag();
+
+ // TODO: reset the IcyInputStream?
+}
+
+inline void
+CurlInputStream::HeaderReceived(const char *name, std::string &&value)
+{
+ if (IsSeekPending())
+ /* don't update metadata while seeking */
+ return;
+
+ if (StringEqualsCaseASCII(name, "accept-ranges")) {
+ /* a stream with icy-metadata is not seekable */
+ if (!icy->IsEnabled())
+ seekable = true;
+ } else if (StringEqualsCaseASCII(name, "content-length")) {
+ size = offset + ParseUint64(value.c_str());
+ } else if (StringEqualsCaseASCII(name, "content-type")) {
+ SetMimeType(std::move(value));
+ } else if (StringEqualsCaseASCII(name, "icy-name") ||
+ StringEqualsCaseASCII(name, "ice-name") ||
+ StringEqualsCaseASCII(name, "x-audiocast-name")) {
+ TagBuilder tag_builder;
+ tag_builder.AddItem(TAG_NAME, value.c_str());
+
+ SetTag(tag_builder.CommitNew());
+ } else if (StringEqualsCaseASCII(name, "icy-metaint")) {
+ if (icy->IsEnabled())
+ return;
+
+ size_t icy_metaint = ParseUint64(value.c_str());
+ FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint);
+
+ if (icy_metaint > 0) {
+ icy->Enable(icy_metaint);
+
+ /* a stream with icy-metadata is not
+ seekable */
+ seekable = false;
+ }
+ }
+}
+
+/** called by curl when new data is available */
+static size_t
+input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
+{
+ CurlInputStream &c = *(CurlInputStream *)stream;
+
+ size *= nmemb;
+
+ const char *header = (const char *)ptr;
+ if (size > 5 && memcmp(header, "HTTP/", 5) == 0) {
+ c.ResponseBoundary();
+ return size;
+ }
+
+ const char *end = header + size;
+
+ char name[64];
+
+ const char *value = (const char *)memchr(header, ':', size);
+ if (value == nullptr || (size_t)(value - header) >= sizeof(name))
+ return size;
+
+ memcpy(name, header, value - header);
+ name[value - header] = 0;
+
+ /* skip the colon */
+
+ ++value;
+
+ /* strip the value */
+
+ value = StripLeft(value, end);
+ end = StripRight(value, end);
+
+ c.HeaderReceived(name, std::string(value, end));
+ return size;
+}
+
+inline size_t
+CurlInputStream::DataReceived(const void *ptr, size_t received_size)
+{
+ assert(received_size > 0);
+
+ const ScopeLock protect(mutex);
+
+ if (IsSeekPending())
+ SeekDone();
+
+ if (received_size > GetBufferSpace()) {
+ AsyncInputStream::Pause();
+ return CURL_WRITEFUNC_PAUSE;
+ }
+
+ AppendToBuffer(ptr, received_size);
+ return received_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)
+{
+ CurlInputStream &c = *(CurlInputStream *)stream;
+
+ size *= nmemb;
+ if (size == 0)
+ return 0;
+
+ return c.DataReceived(ptr, size);
+}
+
+bool
+CurlInputStream::InitEasy(Error &error)
+{
+ easy = curl_easy_init();
+ if (easy == nullptr) {
+ error.Set(curl_domain, "curl_easy_init() failed");
+ return false;
+ }
+
+ curl_easy_setopt(easy, CURLOPT_PRIVATE, (void *)this);
+ curl_easy_setopt(easy, CURLOPT_USERAGENT,
+ "Music Player Daemon " VERSION);
+ curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION,
+ input_curl_headerfunction);
+ curl_easy_setopt(easy, CURLOPT_WRITEHEADER, this);
+ curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION,
+ input_curl_writefunction);
+ curl_easy_setopt(easy, CURLOPT_WRITEDATA, this);
+ curl_easy_setopt(easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
+ curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1l);
+ curl_easy_setopt(easy, CURLOPT_NETRC, 1l);
+ curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5l);
+ curl_easy_setopt(easy, CURLOPT_FAILONERROR, 1l);
+ curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, error_buffer);
+ curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1l);
+ curl_easy_setopt(easy, CURLOPT_NOSIGNAL, 1l);
+ curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT, 10l);
+
+ if (proxy != nullptr)
+ curl_easy_setopt(easy, CURLOPT_PROXY, proxy);
+
+ if (proxy_port > 0)
+ curl_easy_setopt(easy, CURLOPT_PROXYPORT, (long)proxy_port);
+
+ if (proxy_user != nullptr && proxy_password != nullptr) {
+ char proxy_auth_str[1024];
+ snprintf(proxy_auth_str, sizeof(proxy_auth_str),
+ "%s:%s",
+ proxy_user, proxy_password);
+ curl_easy_setopt(easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
+ }
+
+ curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
+ curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
+
+ CURLcode code = curl_easy_setopt(easy, CURLOPT_URL, GetURI());
+ if (code != CURLE_OK) {
+ error.Format(curl_domain, code,
+ "curl_easy_setopt() failed: %s",
+ curl_easy_strerror(code));
+ return false;
+ }
+
+ request_headers = nullptr;
+ request_headers = curl_slist_append(request_headers,
+ "Icy-Metadata: 1");
+ curl_easy_setopt(easy, CURLOPT_HTTPHEADER, request_headers);
+
+ return true;
+}
+
+void
+CurlInputStream::DoSeek(offset_type new_offset)
+{
+ assert(IsReady());
+
+ /* close the old connection and open a new one */
+
+ mutex.unlock();
+
+ FreeEasyIndirect();
+
+ offset = new_offset;
+ if (offset == size) {
+ /* seek to EOF: simulate empty result; avoid
+ triggering a "416 Requested Range Not Satisfiable"
+ response */
+ mutex.lock();
+ SeekDone();
+ return;
+ }
+
+ Error error;
+ if (!InitEasy(postponed_error)) {
+ mutex.lock();
+ PostponeError(std::move(error));
+ return;
+ }
+
+ /* send the "Range" header */
+
+ if (offset > 0) {
+ sprintf(range, "%lld-", (long long)offset);
+ curl_easy_setopt(easy, CURLOPT_RANGE, range);
+ }
+
+ if (!input_curl_easy_add_indirect(this, error)) {
+ mutex.lock();
+ PostponeError(std::move(error));
+ return;
+ }
+
+ mutex.lock();
+ offset = new_offset;
+}
+
+inline InputStream *
+CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ void *buffer = HugeAllocate(CURL_MAX_BUFFERED);
+ if (buffer == nullptr) {
+ error.Set(curl_domain, "Out of memory");
+ return nullptr;
+ }
+
+ CurlInputStream *c = new CurlInputStream(url, mutex, cond, buffer);
+
+ if (!c->InitEasy(error) || !input_curl_easy_add_indirect(c, error)) {
+ delete c;
+ return nullptr;
+ }
+
+ return c->icy;
+}
+
+static InputStream *
+input_curl_open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (memcmp(url, "http://", 7) != 0 &&
+ memcmp(url, "https://", 8) != 0)
+ return nullptr;
+
+ return CurlInputStream::Open(url, mutex, cond, error);
+}
+
+const struct InputPlugin input_plugin_curl = {
+ "curl",
+ input_curl_init,
+ input_curl_finish,
+ input_curl_open,
+};
diff --git a/src/input/plugins/CurlInputPlugin.hxx b/src/input/plugins/CurlInputPlugin.hxx
new file mode 100644
index 000000000..4acb18bfc
--- /dev/null
+++ b/src/input/plugins/CurlInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_CURL_HXX
+#define MPD_INPUT_CURL_HXX
+
+extern const struct InputPlugin input_plugin_curl;
+
+#endif
diff --git a/src/input/plugins/DespotifyInputPlugin.cxx b/src/input/plugins/DespotifyInputPlugin.cxx
new file mode 100644
index 000000000..29d9186d0
--- /dev/null
+++ b/src/input/plugins/DespotifyInputPlugin.cxx
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DespotifyInputPlugin.hxx"
+#include "lib/despotify/DespotifyUtils.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "tag/Tag.hxx"
+#include "util/StringUtil.hxx"
+#include "Log.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <stdio.h>
+
+class DespotifyInputStream final : public InputStream {
+ 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)
+ :InputStream(_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 */
+ SetMimeType("audio/x-mpd-cdda-pcm");
+ SetReady();
+ }
+
+public:
+ ~DespotifyInputStream();
+
+ static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
+ Error &error);
+
+ void Callback(int sig);
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return eof;
+ }
+
+ Tag *ReadTag() override {
+ if (tag.IsEmpty())
+ return nullptr;
+
+ Tag *result = new Tag(std::move(tag));
+ tag.Clear();
+ return result;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+
+private:
+ void FillBuffer();
+};
+
+inline void
+DespotifyInputStream::FillBuffer()
+{
+ /* Wait until there is data */
+ while (1) {
+ int rc = despotify_get_pcm(session, &pcm);
+
+ if (rc == 0 && pcm.len) {
+ len_available = pcm.len;
+ break;
+ }
+
+ if (eof == true)
+ break;
+
+ if (rc < 0) {
+ LogDebug(despotify_domain, "despotify_get_pcm error");
+ eof = true;
+ break;
+ }
+
+ /* Wait a while until next iteration */
+ usleep(50 * 1000);
+ }
+}
+
+inline void
+DespotifyInputStream::Callback(int sig)
+{
+ switch (sig) {
+ case DESPOTIFY_NEW_TRACK:
+ break;
+
+ case DESPOTIFY_TIME_TELL:
+ break;
+
+ case DESPOTIFY_TRACK_PLAY_ERROR:
+ LogWarning(despotify_domain, "Track play error");
+ eof = true;
+ len_available = 0;
+ break;
+
+ case DESPOTIFY_END_OF_PLAYLIST:
+ eof = true;
+ LogDebug(despotify_domain, "End of playlist");
+ break;
+ }
+}
+
+static void callback(gcc_unused struct despotify_session* ds,
+ int sig, gcc_unused void* data, void* callback_data)
+{
+ DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
+
+ ctx->Callback(sig);
+}
+
+DespotifyInputStream::~DespotifyInputStream()
+{
+ mpd_despotify_unregister_callback(callback);
+ despotify_free_track(track);
+}
+
+inline InputStream *
+DespotifyInputStream::Open(const char *url,
+ Mutex &mutex, Cond &cond,
+ gcc_unused Error &error)
+{
+ if (!StringStartsWith(url, "spt://"))
+ return nullptr;
+
+ despotify_session *session = mpd_despotify_get_session();
+ if (session == nullptr)
+ return nullptr;
+
+ ds_link *ds_link = despotify_link_from_uri(url + 6);
+ if (!ds_link) {
+ FormatDebug(despotify_domain, "Can't find %s", url);
+ return nullptr;
+ }
+ if (ds_link->type != LINK_TYPE_TRACK) {
+ despotify_free_link(ds_link);
+ return nullptr;
+ }
+
+ ds_track *track = despotify_link_get_track(session, ds_link);
+ despotify_free_link(ds_link);
+ if (!track)
+ return nullptr;
+
+ DespotifyInputStream *ctx =
+ new DespotifyInputStream(url, mutex, cond,
+ session, track);
+
+ if (!mpd_despotify_register_callback(callback, ctx)) {
+ delete ctx;
+ return nullptr;
+ }
+
+ if (despotify_play(ctx->session, ctx->track, false) == false) {
+ mpd_despotify_unregister_callback(callback);
+ delete ctx;
+ return nullptr;
+ }
+
+ return ctx;
+}
+
+static InputStream *
+input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error)
+{
+ return DespotifyInputStream::Open(url, mutex, cond, error);
+}
+
+size_t
+DespotifyInputStream::Read(void *ptr, size_t read_size,
+ gcc_unused Error &error)
+{
+ if (len_available == 0)
+ FillBuffer();
+
+ size_t to_cpy = std::min(read_size, len_available);
+ memcpy(ptr, pcm.buf, to_cpy);
+ len_available -= to_cpy;
+
+ offset += to_cpy;
+
+ return to_cpy;
+}
+
+const InputPlugin input_plugin_despotify = {
+ "despotify",
+ nullptr,
+ nullptr,
+ input_despotify_open,
+};
diff --git a/src/input/plugins/DespotifyInputPlugin.hxx b/src/input/plugins/DespotifyInputPlugin.hxx
new file mode 100644
index 000000000..83f963520
--- /dev/null
+++ b/src/input/plugins/DespotifyInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef INPUT_DESPOTIFY_HXX
+#define INPUT_DESPOTIFY_HXX
+
+extern const struct InputPlugin input_plugin_despotify;
+
+#endif
diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx
new file mode 100644
index 000000000..669f8d403
--- /dev/null
+++ b/src/input/plugins/FfmpegInputPlugin.cxx
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* necessary because libavutil/common.h uses UINT64_C */
+#define __STDC_CONSTANT_MACROS
+
+#include "config.h"
+#include "FfmpegInputPlugin.hxx"
+#include "lib/ffmpeg/Domain.hxx"
+#include "lib/ffmpeg/Error.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <libavformat/avio.h>
+#include <libavformat/avformat.h>
+}
+
+struct FfmpegInputStream final : public InputStream {
+ AVIOContext *h;
+
+ bool eof;
+
+ FfmpegInputStream(const char *_uri, Mutex &_mutex, Cond &_cond,
+ AVIOContext *_h)
+ :InputStream(_uri, _mutex, _cond),
+ h(_h), eof(false) {
+ seekable = (h->seekable & AVIO_SEEKABLE_NORMAL) != 0;
+ 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 */
+ SetMimeType("audio/x-mpd-ffmpeg");
+ SetReady();
+ }
+
+ ~FfmpegInputStream() {
+ avio_close(h);
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override;
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+static inline bool
+input_ffmpeg_supported(void)
+{
+ void *opaque = nullptr;
+ return avio_enum_protocols(&opaque, 0) != nullptr;
+}
+
+static InputPlugin::InitResult
+input_ffmpeg_init(gcc_unused const config_param &param,
+ Error &error)
+{
+ av_register_all();
+
+ /* disable this plugin if there's no registered protocol */
+ if (!input_ffmpeg_supported()) {
+ error.Set(ffmpeg_domain, "No protocol");
+ return InputPlugin::InitResult::UNAVAILABLE;
+ }
+
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static InputStream *
+input_ffmpeg_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "gopher://") &&
+ !StringStartsWith(uri, "rtp://") &&
+ !StringStartsWith(uri, "rtsp://") &&
+ !StringStartsWith(uri, "rtmp://") &&
+ !StringStartsWith(uri, "rtmpt://") &&
+ !StringStartsWith(uri, "rtmps://"))
+ return nullptr;
+
+ AVIOContext *h;
+ auto result = avio_open(&h, uri, AVIO_FLAG_READ);
+ if (result != 0) {
+ SetFfmpegError(error, result);
+ return nullptr;
+ }
+
+ return new FfmpegInputStream(uri, mutex, cond, h);
+}
+
+size_t
+FfmpegInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ auto result = avio_read(h, (unsigned char *)ptr, read_size);
+ if (result <= 0) {
+ if (result < 0)
+ SetFfmpegError(error, result, "avio_read() failed");
+
+ eof = true;
+ return false;
+ }
+
+ offset += result;
+ return (size_t)result;
+}
+
+bool
+FfmpegInputStream::IsEOF()
+{
+ return eof;
+}
+
+bool
+FfmpegInputStream::Seek(offset_type new_offset, Error &error)
+{
+ auto result = avio_seek(h, new_offset, SEEK_SET);
+
+ if (result < 0) {
+ SetFfmpegError(error, result, "avio_seek() failed");
+ return false;
+ }
+
+ offset = result;
+ eof = false;
+ return true;
+}
+
+const InputPlugin input_plugin_ffmpeg = {
+ "ffmpeg",
+ input_ffmpeg_init,
+ nullptr,
+ input_ffmpeg_open,
+};
diff --git a/src/input/plugins/FfmpegInputPlugin.hxx b/src/input/plugins/FfmpegInputPlugin.hxx
new file mode 100644
index 000000000..43f829e89
--- /dev/null
+++ b/src/input/plugins/FfmpegInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_INPUT_PLUGIN_HXX
+#define MPD_FFMPEG_INPUT_PLUGIN_HXX
+
+/**
+ * An input plugin based on libavformat's "avio" library.
+ */
+extern const struct InputPlugin input_plugin_ffmpeg;
+
+#endif
diff --git a/src/input/plugins/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx
new file mode 100644
index 000000000..867b5722d
--- /dev/null
+++ b/src/input/plugins/FileInputPlugin.cxx
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "FileInputPlugin.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/Path.hxx"
+#include "system/fd_util.h"
+#include "open.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+static constexpr Domain file_domain("file");
+
+class FileInputStream final : public InputStream {
+ const int fd;
+
+public:
+ FileInputStream(const char *path, int _fd, off_t _size,
+ Mutex &_mutex, Cond &_cond)
+ :InputStream(path, _mutex, _cond),
+ fd(_fd) {
+ size = _size;
+ seekable = true;
+ SetReady();
+ }
+
+ ~FileInputStream() {
+ close(fd);
+ }
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return GetOffset() >= GetSize();
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+InputStream *
+OpenFileInputStream(Path path,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ const int fd = OpenFile(path, O_RDONLY|O_BINARY, 0);
+ if (fd < 0) {
+ error.FormatErrno("Failed to open \"%s\"",
+ path.c_str());
+ return nullptr;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ error.FormatErrno("Failed to stat \"%s\"", path.c_str());
+ close(fd);
+ return nullptr;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ error.Format(file_domain, "Not a regular file: %s",
+ path.c_str());
+ close(fd);
+ return nullptr;
+ }
+
+#ifdef POSIX_FADV_SEQUENTIAL
+ posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL);
+#endif
+
+ return new FileInputStream(path.c_str(), fd, st.st_size, mutex, cond);
+}
+
+static InputStream *
+input_file_open(gcc_unused const char *filename,
+ gcc_unused Mutex &mutex, gcc_unused Cond &cond,
+ gcc_unused Error &error)
+{
+ /* dummy method; use OpenFileInputStream() instead */
+
+ return nullptr;
+}
+
+bool
+FileInputStream::Seek(offset_type new_offset, Error &error)
+{
+ auto result = lseek(fd, (off_t)new_offset, SEEK_SET);
+ if (result < 0) {
+ error.SetErrno("Failed to seek");
+ return false;
+ }
+
+ offset = (offset_type)result;
+ return true;
+}
+
+size_t
+FileInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ ssize_t nbytes = read(fd, ptr, read_size);
+ if (nbytes < 0) {
+ error.SetErrno("Failed to read");
+ return 0;
+ }
+
+ offset += nbytes;
+ return (size_t)nbytes;
+}
+
+const InputPlugin input_plugin_file = {
+ "file",
+ nullptr,
+ nullptr,
+ input_file_open,
+};
diff --git a/src/input/plugins/FileInputPlugin.hxx b/src/input/plugins/FileInputPlugin.hxx
new file mode 100644
index 000000000..ee194ec34
--- /dev/null
+++ b/src/input/plugins/FileInputPlugin.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_FILE_HXX
+#define MPD_INPUT_FILE_HXX
+
+class InputStream;
+class Path;
+class Mutex;
+class Cond;
+class Error;
+
+extern const struct InputPlugin input_plugin_file;
+
+InputStream *
+OpenFileInputStream(Path path,
+ Mutex &mutex, Cond &cond,
+ Error &error);
+
+#endif
diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx
new file mode 100644
index 000000000..df291bc84
--- /dev/null
+++ b/src/input/plugins/MmsInputPlugin.cxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MmsInputPlugin.hxx"
+#include "input/ThreadInputStream.hxx"
+#include "input/InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <libmms/mmsx.h>
+
+static constexpr size_t MMS_BUFFER_SIZE = 256 * 1024;
+
+class MmsInputStream final : public ThreadInputStream {
+ mmsx_t *mms;
+
+public:
+ MmsInputStream(const char *_uri, Mutex &_mutex, Cond &_cond)
+ :ThreadInputStream(input_plugin_mms.name, _uri, _mutex, _cond,
+ MMS_BUFFER_SIZE) {
+ }
+
+protected:
+ virtual bool Open(gcc_unused Error &error) override;
+ virtual size_t ThreadRead(void *ptr, size_t size,
+ Error &error) override;
+
+ virtual void Close() {
+ mmsx_close(mms);
+ }
+};
+
+static constexpr Domain mms_domain("mms");
+
+bool
+MmsInputStream::Open(Error &error)
+{
+ Unlock();
+
+ mms = mmsx_connect(nullptr, nullptr, GetURI(), 128 * 1024);
+ if (mms == nullptr) {
+ Lock();
+ error.Set(mms_domain, "mmsx_connect() failed");
+ return false;
+ }
+
+ Lock();
+
+ /* TODO: is this correct? at least this selects the ffmpeg
+ decoder, which seems to work fine */
+ SetMimeType("audio/x-ms-wma");
+ return true;
+}
+
+static InputStream *
+input_mms_open(const char *url,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(url, "mms://") &&
+ !StringStartsWith(url, "mmsh://") &&
+ !StringStartsWith(url, "mmst://") &&
+ !StringStartsWith(url, "mmsu://"))
+ return nullptr;
+
+ auto m = new MmsInputStream(url, mutex, cond);
+ auto is = m->Start(error);
+ if (is == nullptr)
+ delete m;
+
+ return is;
+}
+
+size_t
+MmsInputStream::ThreadRead(void *ptr, size_t read_size, Error &error)
+{
+ /* unfortunately, mmsx_read() blocks until the whole buffer
+ has been filled; to avoid big latencies, limit the size of
+ each chunk we read to a reasonable size */
+ constexpr size_t MAX_CHUNK = 16384;
+ if (read_size > MAX_CHUNK)
+ read_size = MAX_CHUNK;
+
+ int nbytes = mmsx_read(nullptr, mms, (char *)ptr, read_size);
+ if (nbytes <= 0) {
+ if (nbytes < 0)
+ error.SetErrno("mmsx_read() failed");
+ return 0;
+ }
+
+ return (size_t)nbytes;
+}
+
+const InputPlugin input_plugin_mms = {
+ "mms",
+ nullptr,
+ nullptr,
+ input_mms_open,
+};
diff --git a/src/input/plugins/MmsInputPlugin.hxx b/src/input/plugins/MmsInputPlugin.hxx
new file mode 100644
index 000000000..b4017ffd6
--- /dev/null
+++ b/src/input/plugins/MmsInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef INPUT_MMS_H
+#define INPUT_MMS_H
+
+extern const struct InputPlugin input_plugin_mms;
+
+#endif
diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx
new file mode 100644
index 000000000..c6c0970b9
--- /dev/null
+++ b/src/input/plugins/NfsInputPlugin.cxx
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NfsInputPlugin.hxx"
+#include "../AsyncInputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "lib/nfs/Domain.hxx"
+#include "lib/nfs/Glue.hxx"
+#include "lib/nfs/FileReader.hxx"
+#include "util/HugeAllocator.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+}
+
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+/**
+ * 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 NFS_MAX_BUFFERED = 512 * 1024;
+
+/**
+ * Resume the stream at this number of bytes after it has been paused.
+ */
+static const size_t NFS_RESUME_AT = 384 * 1024;
+
+class NfsInputStream final : public AsyncInputStream, NfsFileReader {
+ uint64_t next_offset;
+
+ bool reconnect_on_resume, reconnecting;
+
+public:
+ NfsInputStream(const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer)
+ :AsyncInputStream(_uri, _mutex, _cond,
+ _buffer, NFS_MAX_BUFFERED,
+ NFS_RESUME_AT),
+ reconnect_on_resume(false), reconnecting(false) {}
+
+ virtual ~NfsInputStream() {
+ DeferClose();
+ }
+
+ bool Open(Error &error) {
+ assert(!IsReady());
+
+ return NfsFileReader::Open(GetURI(), error);
+ }
+
+private:
+ bool DoRead();
+
+protected:
+ /* virtual methods from AsyncInputStream */
+ virtual void DoResume() override;
+ virtual void DoSeek(offset_type new_offset) override;
+
+private:
+ /* virtual methods from NfsFileReader */
+ void OnNfsFileOpen(uint64_t size) override;
+ void OnNfsFileRead(const void *data, size_t size) override;
+ void OnNfsFileError(Error &&error) override;
+};
+
+bool
+NfsInputStream::DoRead()
+{
+ assert(NfsFileReader::IsIdle());
+
+ int64_t remaining = size - next_offset;
+ if (remaining <= 0)
+ return true;
+
+ const size_t buffer_space = GetBufferSpace();
+ if (buffer_space == 0) {
+ Pause();
+ return true;
+ }
+
+ size_t nbytes = std::min<size_t>(std::min<uint64_t>(remaining, 32768),
+ buffer_space);
+
+ mutex.unlock();
+ Error error;
+ bool success = NfsFileReader::Read(next_offset, nbytes, error);
+ mutex.lock();
+
+ if (!success) {
+ PostponeError(std::move(error));
+ return false;
+ }
+
+ return true;
+}
+
+void
+NfsInputStream::DoResume()
+{
+ if (reconnect_on_resume) {
+ /* the NFS connection has died while this stream was
+ "paused" - attempt to reconnect */
+
+ reconnect_on_resume = false;
+ reconnecting = true;
+
+ mutex.unlock();
+ NfsFileReader::Close();
+
+ Error error;
+ bool success = NfsFileReader::Open(GetURI(), error);
+ mutex.lock();
+
+ if (!success) {
+ postponed_error = std::move(error);
+ cond.broadcast();
+ }
+
+ return;
+ }
+
+ assert(NfsFileReader::IsIdle());
+
+ DoRead();
+}
+
+void
+NfsInputStream::DoSeek(offset_type new_offset)
+{
+ mutex.unlock();
+ NfsFileReader::CancelRead();
+ mutex.lock();
+
+ next_offset = offset = new_offset;
+ SeekDone();
+ DoRead();
+}
+
+void
+NfsInputStream::OnNfsFileOpen(uint64_t _size)
+{
+ const ScopeLock protect(mutex);
+
+ if (reconnecting) {
+ /* reconnect has succeeded */
+
+ reconnecting = false;
+ DoRead();
+ return;
+ }
+
+ size = _size;
+ seekable = true;
+ next_offset = 0;
+ SetReady();
+ DoRead();
+}
+
+void
+NfsInputStream::OnNfsFileRead(const void *data, size_t data_size)
+{
+ const ScopeLock protect(mutex);
+ assert(!IsBufferFull());
+ assert(IsBufferFull() == (GetBufferSpace() == 0));
+ AppendToBuffer(data, data_size);
+
+ next_offset += data_size;
+
+ DoRead();
+}
+
+void
+NfsInputStream::OnNfsFileError(Error &&error)
+{
+ const ScopeLock protect(mutex);
+
+ if (IsPaused()) {
+ /* while we're paused, don't report this error to the
+ client just yet (it might just be timeout, maybe
+ playback has been paused for quite some time) -
+ wait until the stream gets resumed and try to
+ reconnect, to give it another chance */
+
+ reconnect_on_resume = true;
+ return;
+ }
+
+ postponed_error = std::move(error);
+
+ if (IsSeekPending())
+ SeekDone();
+ else if (!IsReady())
+ SetReady();
+ else
+ cond.broadcast();
+}
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputPlugin::InitResult
+input_nfs_init(const config_param &, Error &)
+{
+ nfs_init();
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static void
+input_nfs_finish()
+{
+ nfs_finish();
+}
+
+static InputStream *
+input_nfs_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "nfs://"))
+ return nullptr;
+
+ void *buffer = HugeAllocate(NFS_MAX_BUFFERED);
+ if (buffer == nullptr) {
+ error.Set(nfs_domain, "Out of memory");
+ return nullptr;
+ }
+
+ NfsInputStream *is = new NfsInputStream(uri, mutex, cond, buffer);
+ if (!is->Open(error)) {
+ delete is;
+ return nullptr;
+ }
+
+ return is;
+}
+
+const InputPlugin input_plugin_nfs = {
+ "nfs",
+ input_nfs_init,
+ input_nfs_finish,
+ input_nfs_open,
+};
diff --git a/src/input/plugins/NfsInputPlugin.hxx b/src/input/plugins/NfsInputPlugin.hxx
new file mode 100644
index 000000000..d2cc87549
--- /dev/null
+++ b/src/input/plugins/NfsInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_NFS_H
+#define MPD_INPUT_NFS_H
+
+extern const struct InputPlugin input_plugin_nfs;
+
+#endif
diff --git a/src/input/plugins/RewindInputPlugin.cxx b/src/input/plugins/RewindInputPlugin.cxx
new file mode 100644
index 000000000..95f604044
--- /dev/null
+++ b/src/input/plugins/RewindInputPlugin.cxx
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RewindInputPlugin.hxx"
+#include "../ProxyInputStream.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+class RewindInputStream final : public ProxyInputStream {
+ /**
+ * 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];
+
+public:
+ RewindInputStream(InputStream *_input)
+ :ProxyInputStream(_input),
+ tail(0) {
+ }
+
+ /* virtual methods from InputStream */
+
+ void Update() override {
+ if (!ReadingFromBuffer())
+ ProxyInputStream::Update();
+ }
+
+ bool IsEOF() override {
+ return !ReadingFromBuffer() && ProxyInputStream::IsEOF();
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+
+private:
+ /**
+ * 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 && offset < input.GetOffset();
+ }
+};
+
+size_t
+RewindInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ if (ReadingFromBuffer()) {
+ /* buffered read */
+
+ assert(head == (size_t)offset);
+ assert(tail == (size_t)input.GetOffset());
+
+ if (read_size > tail - head)
+ read_size = tail - head;
+
+ memcpy(ptr, buffer + head, read_size);
+ head += read_size;
+ offset += read_size;
+
+ return read_size;
+ } else {
+ /* pass method call to underlying stream */
+
+ size_t nbytes = input.Read(ptr, read_size, error);
+
+ if (input.GetOffset() > (offset_type)sizeof(buffer))
+ /* disable buffering */
+ tail = 0;
+ else if (tail == (size_t)offset) {
+ /* append to buffer */
+
+ memcpy(buffer + tail, ptr, nbytes);
+ tail += nbytes;
+
+ assert(tail == (size_t)input.GetOffset());
+ }
+
+ CopyAttributes();
+
+ return nbytes;
+ }
+}
+
+bool
+RewindInputStream::Seek(offset_type new_offset,
+ Error &error)
+{
+ assert(IsReady());
+
+ if (tail > 0 && new_offset <= (offset_type)tail) {
+ /* buffered seek */
+
+ assert(!ReadingFromBuffer() ||
+ head == (size_t)offset);
+ assert(tail == (size_t)input.GetOffset());
+
+ head = (size_t)new_offset;
+ offset = new_offset;
+
+ return true;
+ } else {
+ /* disable the buffer, because input has left the
+ buffered range now */
+ tail = 0;
+
+ return ProxyInputStream::Seek(new_offset, error);
+ }
+}
+
+InputStream *
+input_rewind_open(InputStream *is)
+{
+ assert(is != nullptr);
+ assert(!is->IsReady() || is->GetOffset() == 0);
+
+ if (is->IsReady() && is->IsSeekable())
+ /* seekable resources don't need this plugin */
+ return is;
+
+ return new RewindInputStream(is);
+}
diff --git a/src/input/plugins/RewindInputPlugin.hxx b/src/input/plugins/RewindInputPlugin.hxx
new file mode 100644
index 000000000..56b01b585
--- /dev/null
+++ b/src/input/plugins/RewindInputPlugin.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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"
+
+class InputStream;
+
+InputStream *
+input_rewind_open(InputStream *is);
+
+#endif
diff --git a/src/input/plugins/SmbclientInputPlugin.cxx b/src/input/plugins/SmbclientInputPlugin.cxx
new file mode 100644
index 000000000..79987180f
--- /dev/null
+++ b/src/input/plugins/SmbclientInputPlugin.cxx
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SmbclientInputPlugin.hxx"
+#include "lib/smbclient/Init.hxx"
+#include "lib/smbclient/Mutex.hxx"
+#include "../InputStream.hxx"
+#include "../InputPlugin.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <libsmbclient.h>
+
+class SmbclientInputStream final : public InputStream {
+ SMBCCTX *ctx;
+ int fd;
+
+public:
+ SmbclientInputStream(const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ SMBCCTX *_ctx, int _fd, const struct stat &st)
+ :InputStream(_uri, _mutex, _cond),
+ ctx(_ctx), fd(_fd) {
+ seekable = true;
+ size = st.st_size;
+ SetReady();
+ }
+
+ ~SmbclientInputStream() {
+ smbclient_mutex.lock();
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ smbclient_mutex.unlock();
+ }
+
+ /* virtual methods from InputStream */
+
+ bool IsEOF() override {
+ return offset >= size;
+ }
+
+ size_t Read(void *ptr, size_t size, Error &error) override;
+ bool Seek(offset_type offset, Error &error) override;
+};
+
+/*
+ * InputPlugin methods
+ *
+ */
+
+static InputPlugin::InitResult
+input_smbclient_init(gcc_unused const config_param &param, Error &error)
+{
+ if (!SmbclientInit(error))
+ return InputPlugin::InitResult::UNAVAILABLE;
+
+ // TODO: create one global SMBCCTX here?
+
+ // TODO: evaluate config_param, call smbc_setOption*()
+
+ return InputPlugin::InitResult::SUCCESS;
+}
+
+static InputStream *
+input_smbclient_open(const char *uri,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ if (!StringStartsWith(uri, "smb://"))
+ return nullptr;
+
+ const ScopeLock protect(smbclient_mutex);
+
+ SMBCCTX *ctx = smbc_new_context();
+ if (ctx == nullptr) {
+ error.SetErrno("smbc_new_context() failed");
+ return nullptr;
+ }
+
+ SMBCCTX *ctx2 = smbc_init_context(ctx);
+ if (ctx2 == nullptr) {
+ error.SetErrno("smbc_init_context() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ ctx = ctx2;
+
+ int fd = smbc_open(uri, O_RDONLY, 0);
+ if (fd < 0) {
+ error.SetErrno("smbc_open() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ struct stat st;
+ if (smbc_fstat(fd, &st) < 0) {
+ error.SetErrno("smbc_fstat() failed");
+ smbc_close(fd);
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ return new SmbclientInputStream(uri, mutex, cond, ctx, fd, st);
+}
+
+size_t
+SmbclientInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ smbclient_mutex.lock();
+ ssize_t nbytes = smbc_read(fd, ptr, read_size);
+ smbclient_mutex.unlock();
+ if (nbytes < 0) {
+ error.SetErrno("smbc_read() failed");
+ nbytes = 0;
+ }
+
+ return nbytes;
+}
+
+bool
+SmbclientInputStream::Seek(offset_type new_offset, Error &error)
+{
+ smbclient_mutex.lock();
+ off_t result = smbc_lseek(fd, new_offset, SEEK_SET);
+ smbclient_mutex.unlock();
+ if (result < 0) {
+ error.SetErrno("smbc_lseek() failed");
+ return false;
+ }
+
+ offset = result;
+ return true;
+}
+
+const InputPlugin input_plugin_smbclient = {
+ "smbclient",
+ input_smbclient_init,
+ nullptr,
+ input_smbclient_open,
+};
diff --git a/src/input/plugins/SmbclientInputPlugin.hxx b/src/input/plugins/SmbclientInputPlugin.hxx
new file mode 100644
index 000000000..a0539d020
--- /dev/null
+++ b/src/input/plugins/SmbclientInputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_INPUT_SMBCLIENT_H
+#define MPD_INPUT_SMBCLIENT_H
+
+extern const struct InputPlugin input_plugin_smbclient;
+
+#endif
diff --git a/src/java/Class.hxx b/src/java/Class.hxx
new file mode 100644
index 000000000..9496ee67d
--- /dev/null
+++ b/src/java/Class.hxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010-2011 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 JAVA_CLASS_HXX
+#define JAVA_CLASS_HXX
+
+#include "Ref.hxx"
+
+#include <assert.h>
+
+namespace Java {
+ /**
+ * Wrapper for a local "jclass" reference.
+ */
+ class Class : public Java::LocalRef<jclass> {
+ public:
+ Class(JNIEnv *env, jclass cls)
+ :LocalRef<jclass>(env, cls) {}
+
+ Class(JNIEnv *env, const char *name)
+ :LocalRef<jclass>(env, env->FindClass(name)) {}
+ };
+
+ /**
+ * Wrapper for a global "jclass" reference.
+ */
+ class TrivialClass : public TrivialRef<jclass> {
+ public:
+ void Find(JNIEnv *env, const char *name) {
+ assert(env != nullptr);
+ assert(name != nullptr);
+
+ jclass cls = env->FindClass(name);
+ assert(cls != nullptr);
+
+ Set(env, cls);
+ env->DeleteLocalRef(cls);
+ }
+
+ bool FindOptional(JNIEnv *env, const char *name) {
+ assert(env != nullptr);
+ assert(name != nullptr);
+
+ jclass cls = env->FindClass(name);
+ if (cls == nullptr) {
+ env->ExceptionClear();
+ return false;
+ }
+
+ Set(env, cls);
+ env->DeleteLocalRef(cls);
+ return true;
+ }
+ };
+}
+
+#endif
diff --git a/src/java/Exception.hxx b/src/java/Exception.hxx
new file mode 100644
index 000000000..adbdebb47
--- /dev/null
+++ b/src/java/Exception.hxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef JAVA_EXCEPTION_HXX
+#define JAVA_EXCEPTION_HXX
+
+#include <jni.h>
+
+namespace Java {
+ /**
+ * Check if an exception has occurred, and discard it.
+ *
+ * @return true if an exception was found (and discarded)
+ */
+ static inline bool DiscardException(JNIEnv *env) {
+ bool result = env->ExceptionCheck();
+ if (result)
+ env->ExceptionClear();
+ return result;
+ }
+}
+
+#endif
diff --git a/src/java/File.cxx b/src/java/File.cxx
new file mode 100644
index 000000000..8e9599dfc
--- /dev/null
+++ b/src/java/File.cxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "File.hxx"
+#include "Class.hxx"
+#include "String.hxx"
+#include "Object.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/Limits.hxx"
+
+jmethodID Java::File::getAbsolutePath_method;
+
+void
+Java::File::Initialise(JNIEnv *env)
+{
+ Class cls(env, "java/io/File");
+
+ getAbsolutePath_method = env->GetMethodID(cls, "getAbsolutePath",
+ "()Ljava/lang/String;");
+}
+
+AllocatedPath
+Java::File::ToAbsolutePath(JNIEnv *env, jobject _file)
+{
+ assert(env != nullptr);
+ assert(_file != nullptr);
+
+ LocalObject file(env, _file);
+
+ const jstring path = getAbsolutePath(env, file);
+ if (path == nullptr) {
+ env->ExceptionClear();
+ return AllocatedPath::Null();
+ }
+
+ Java::String path2(env, path);
+ char buffer[MPD_PATH_MAX];
+ path2.CopyTo(env, buffer, sizeof(buffer));
+ return AllocatedPath::FromUTF8(buffer);
+}
diff --git a/src/java/File.hxx b/src/java/File.hxx
new file mode 100644
index 000000000..a569fe214
--- /dev/null
+++ b/src/java/File.hxx
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef JAVA_FILE_HXX
+#define JAVA_FILE_HXX
+
+#include "Object.hxx"
+
+#include <jni.h>
+
+class AllocatedPath;
+
+namespace Java {
+ /**
+ * Wrapper for a java.io.File object.
+ */
+ class File : public LocalObject {
+ static jmethodID getAbsolutePath_method;
+
+ public:
+ gcc_nonnull_all
+ static void Initialise(JNIEnv *env);
+
+ gcc_nonnull_all
+ static jstring getAbsolutePath(JNIEnv *env, jobject file) {
+ return (jstring)env->CallObjectMethod(file,
+ getAbsolutePath_method);
+ }
+
+ /**
+ * Invoke File.getAbsolutePath() and release the
+ * specified File reference.
+ */
+ gcc_pure gcc_nonnull_all
+ static AllocatedPath ToAbsolutePath(JNIEnv *env,
+ jobject file);
+ };
+}
+
+#endif
diff --git a/src/java/Global.cxx b/src/java/Global.cxx
new file mode 100644
index 000000000..1d1160127
--- /dev/null
+++ b/src/java/Global.cxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010-2011 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.
+ */
+
+#include "Global.hxx"
+
+namespace Java {
+ JavaVM *jvm;
+
+ void Init(JNIEnv *env)
+ {
+ env->GetJavaVM(&jvm);
+ }
+}
diff --git a/src/java/Global.hxx b/src/java/Global.hxx
new file mode 100644
index 000000000..5fdf91daf
--- /dev/null
+++ b/src/java/Global.hxx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010-2011 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 JAVA_GLOBAL_HXX
+#define JAVA_GLOBAL_HXX
+
+#include "Compiler.h"
+
+#include <jni.h>
+
+namespace Java {
+ extern JavaVM *jvm;
+
+ void Init(JNIEnv *env);
+
+ static inline void
+ DetachCurrentThread()
+ {
+ if (jvm != nullptr)
+ jvm->DetachCurrentThread();
+ }
+
+ static inline gcc_pure
+ JNIEnv *GetEnv()
+ {
+ JNIEnv *env;
+ jvm->AttachCurrentThread(&env, nullptr);
+ return env;
+ }
+}
+
+#endif
diff --git a/src/java/Object.hxx b/src/java/Object.hxx
new file mode 100644
index 000000000..226ad7623
--- /dev/null
+++ b/src/java/Object.hxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010-2011 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 JAVA_OBJECT_HXX
+#define JAVA_OBJECT_HXX
+
+#include "Ref.hxx"
+
+#include <jni.h>
+
+namespace Java {
+ /**
+ * Wrapper for a local "jobject" reference.
+ */
+ typedef LocalRef<jobject> LocalObject;
+
+ class Object : public GlobalRef<jobject> {
+ public:
+ /**
+ * Constructs an uninitialized object. The method
+ * set() must be called before it is destructed.
+ */
+ Object() = default;
+
+ Object(JNIEnv *env, jobject obj):GlobalRef<jobject>(env, obj) {}
+ };
+}
+
+#endif
diff --git a/src/java/Ref.hxx b/src/java/Ref.hxx
new file mode 100644
index 000000000..e9966f1c6
--- /dev/null
+++ b/src/java/Ref.hxx
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010-2011 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 JAVA_REF_HXX
+#define JAVA_REF_HXX
+
+#include "Global.hxx"
+
+#include <jni.h>
+
+#include <assert.h>
+
+namespace Java {
+ /**
+ * Hold a local reference on a JNI object.
+ */
+ template<typename T>
+ class LocalRef {
+ JNIEnv *const env;
+ const T value;
+
+ public:
+ /**
+ * The local reference is obtained by the caller.
+ */
+ LocalRef(JNIEnv *_env, T _value):env(_env), value(_value) {
+ assert(env != nullptr);
+ assert(value != nullptr);
+ }
+
+ ~LocalRef() {
+ env->DeleteLocalRef(value);
+ }
+
+ LocalRef(const LocalRef &other) = delete;
+ LocalRef &operator=(const LocalRef &other) = delete;
+
+ T Get() const {
+ return value;
+ }
+
+ operator T() const {
+ return value;
+ }
+ };
+
+ /**
+ * Hold a global reference on a JNI object.
+ */
+ template<typename T>
+ class GlobalRef {
+ T value;
+
+ public:
+ /**
+ * Constructs an uninitialized object. The method set() must be
+ * called before it is destructed.
+ */
+ GlobalRef() = default;
+
+ GlobalRef(JNIEnv *env, T _value):value(_value) {
+ assert(env != nullptr);
+ assert(value != nullptr);
+
+ value = (T)env->NewGlobalRef(value);
+ }
+
+ ~GlobalRef() {
+ GetEnv()->DeleteGlobalRef(value);
+ }
+
+ GlobalRef(const GlobalRef &other) = delete;
+ GlobalRef &operator=(const GlobalRef &other) = delete;
+
+ /**
+ * Sets the object, ignoring the previous value. This is only
+ * allowed once after the default constructor was used.
+ */
+ void Set(JNIEnv *env, T _value) {
+ assert(_value != nullptr);
+
+ value = (T)env->NewGlobalRef(_value);
+ }
+
+ T Get() const {
+ return value;
+ }
+
+ operator T() const {
+ return value;
+ }
+ };
+
+ /**
+ * Container for a global reference to a JNI object that gets
+ * initialised and deinitialised explicitly. Since there is no
+ * implicit initialisation in the default constructor, this is a
+ * trivial C++ class. It should only be used for global variables
+ * that are implicitly initialised with zeroes.
+ */
+ template<typename T>
+ class TrivialRef {
+ T value;
+
+ public:
+ constexpr TrivialRef() {};
+
+ TrivialRef(const TrivialRef &other) = delete;
+ TrivialRef &operator=(const TrivialRef &other) = delete;
+
+ bool IsDefined() const {
+ return value != nullptr;
+ }
+
+ /**
+ * Obtain a global reference on the specified object and store it.
+ * This object must not be set already.
+ */
+ void Set(JNIEnv *env, T _value) {
+ assert(value == nullptr);
+ assert(_value != nullptr);
+
+ value = (T)env->NewGlobalRef(_value);
+ }
+
+ /**
+ * Release the global reference and clear this object.
+ */
+ void Clear(JNIEnv *env) {
+ assert(value != nullptr);
+
+ env->DeleteGlobalRef(value);
+ value = nullptr;
+ }
+
+ /**
+ * Release the global reference and clear this object. It is
+ * allowed to call this method without ever calling Set().
+ */
+ void ClearOptional(JNIEnv *env) {
+ if (value != nullptr)
+ Clear(env);
+ }
+
+ T Get() const {
+ return value;
+ }
+
+ operator T() const {
+ return value;
+ }
+ };
+}
+
+#endif
diff --git a/src/java/String.cxx b/src/java/String.cxx
new file mode 100644
index 000000000..8ffb82d72
--- /dev/null
+++ b/src/java/String.cxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010-2011 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.
+ */
+
+#include "String.hxx"
+#include "util/StringUtil.hxx"
+
+char *
+Java::String::CopyTo(JNIEnv *env, jstring value,
+ char *buffer, size_t max_size)
+{
+ const char *p = env->GetStringUTFChars(value, nullptr);
+ if (p == nullptr)
+ return nullptr;
+
+ char *result = CopyString(buffer, p, max_size);
+ env->ReleaseStringUTFChars(value, p);
+ return result;
+}
diff --git a/src/java/String.hxx b/src/java/String.hxx
new file mode 100644
index 000000000..a58385f50
--- /dev/null
+++ b/src/java/String.hxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010-2011 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 JAVA_STRING_HXX
+#define JAVA_STRING_HXX
+
+#include "Ref.hxx"
+
+#include <jni.h>
+
+#include <assert.h>
+#include <stddef.h>
+
+namespace Java {
+ /**
+ * Wrapper for a local "jstring" reference.
+ */
+ class String : public LocalRef<jstring> {
+ public:
+ String(JNIEnv *env, jstring value)
+ :LocalRef<jstring>(env, value) {}
+
+ String(JNIEnv *_env, const char *_value)
+ :LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
+
+ /**
+ * Copy the value to the specified buffer. Truncates
+ * the value if it does not fit into the buffer.
+ *
+ * @return a pointer to the terminating null byte,
+ * nullptr on error
+ */
+ static char *CopyTo(JNIEnv *env, jstring value,
+ char *buffer, size_t max_size);
+
+ /**
+ * Copy the value to the specified buffer. Truncates
+ * the value if it does not fit into the buffer.
+ *
+ * @return a pointer to the terminating null byte,
+ * nullptr on error
+ */
+ char *CopyTo(JNIEnv *env, char *buffer, size_t max_size) {
+ return CopyTo(env, Get(), buffer, max_size);
+ }
+ };
+}
+
+#endif
diff --git a/src/lib/despotify/DespotifyUtils.cxx b/src/lib/despotify/DespotifyUtils.cxx
new file mode 100644
index 000000000..f67679c50
--- /dev/null
+++ b/src/lib/despotify/DespotifyUtils.cxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "DespotifyUtils.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <stdio.h>
+
+const Domain despotify_domain("despotify");
+
+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, gcc_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] = nullptr;
+ }
+ }
+}
+
+Tag
+mpd_despotify_tag_from_track(const ds_track &track)
+{
+ char tracknum[20];
+ char comment[80];
+ char date[20];
+
+ if (!track.has_meta_data)
+ return Tag();
+
+ TagBuilder tag;
+ snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber);
+ snprintf(date, sizeof(date), "%d", track.year);
+ snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
+ track.file_bitrate / 1000,
+ track.geo_restricted ? "" : "not ");
+ tag.AddItem(TAG_TITLE, track.title);
+ tag.AddItem(TAG_ARTIST, track.artist->name);
+ tag.AddItem(TAG_TRACK, tracknum);
+ tag.AddItem(TAG_ALBUM, track.album);
+ tag.AddItem(TAG_DATE, date);
+ tag.AddItem(TAG_COMMENT, comment);
+ tag.SetDuration(SignedSongTime::FromMS(track.length));
+
+ return tag.Commit();
+}
+
+struct despotify_session *mpd_despotify_get_session(void)
+{
+ const char *user;
+ const char *passwd;
+ bool high_bitrate;
+
+ if (g_session)
+ return g_session;
+
+ user = config_get_string(CONF_DESPOTIFY_USER, nullptr);
+ passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
+ high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
+
+ if (user == nullptr || passwd == nullptr) {
+ LogDebug(despotify_domain,
+ "disabling despotify because account is not configured");
+ return nullptr;
+ }
+
+ if (!despotify_init()) {
+ LogWarning(despotify_domain, "Can't initialize despotify");
+ return nullptr;
+ }
+
+ g_session = despotify_init_client(callback, nullptr,
+ high_bitrate, true);
+ if (!g_session) {
+ LogWarning(despotify_domain,
+ "Can't initialize despotify client");
+ return nullptr;
+ }
+
+ if (!despotify_authenticate(g_session, user, passwd)) {
+ LogWarning(despotify_domain,
+ "Can't authenticate despotify session");
+ despotify_exit(g_session);
+ return nullptr;
+ }
+
+ return g_session;
+}
diff --git a/src/lib/despotify/DespotifyUtils.hxx b/src/lib/despotify/DespotifyUtils.hxx
new file mode 100644
index 000000000..835b901a2
--- /dev/null
+++ b/src/lib/despotify/DespotifyUtils.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DESPOTIFY_H
+#define MPD_DESPOTIFY_H
+
+struct Tag;
+struct despotify_session;
+struct ds_track;
+
+extern const class Domain despotify_domain;
+
+/**
+ * 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 nullptr 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 filled in #Tag structure
+ */
+Tag
+mpd_despotify_tag_from_track(const 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/lib/expat/ExpatParser.cxx b/src/lib/expat/ExpatParser.cxx
new file mode 100644
index 000000000..c6b1abe76
--- /dev/null
+++ b/src/lib/expat/ExpatParser.cxx
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ExpatParser.hxx"
+#include "input/InputStream.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <string.h>
+
+static constexpr Domain expat_domain("expat");
+
+void
+ExpatParser::SetError(Error &error)
+{
+ XML_Error code = XML_GetErrorCode(parser);
+ error.Format(expat_domain, int(code), "XML parser failed: %s",
+ XML_ErrorString(code));
+}
+
+bool
+ExpatParser::Parse(const char *data, size_t length, bool is_final,
+ Error &error)
+{
+ bool success = XML_Parse(parser, data, length,
+ is_final) == XML_STATUS_OK;
+ if (!success)
+ SetError(error);
+
+ return success;
+}
+
+bool
+ExpatParser::Parse(InputStream &is, Error &error)
+{
+ assert(is.IsReady());
+
+ while (true) {
+ char buffer[4096];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error);
+ if (nbytes == 0)
+ break;
+
+ if (!Parse(buffer, nbytes, false, error))
+ return false;
+ }
+
+ if (error.IsDefined())
+ return false;
+
+ return Parse("", 0, true, error);
+}
+
+const char *
+ExpatParser::GetAttribute(const XML_Char **atts,
+ const char *name)
+{
+ for (unsigned i = 0; atts[i] != nullptr; i += 2)
+ if (strcmp(atts[i], name) == 0)
+ return atts[i + 1];
+
+ return nullptr;
+}
+
+const char *
+ExpatParser::GetAttributeCase(const XML_Char **atts,
+ const char *name)
+{
+ for (unsigned i = 0; atts[i] != nullptr; i += 2)
+ if (StringEqualsCaseASCII(atts[i], name))
+ return atts[i + 1];
+
+ return nullptr;
+}
diff --git a/src/lib/expat/ExpatParser.hxx b/src/lib/expat/ExpatParser.hxx
new file mode 100644
index 000000000..9d2ac65e5
--- /dev/null
+++ b/src/lib/expat/ExpatParser.hxx
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EXPAT_HXX
+#define MPD_EXPAT_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <expat.h>
+
+class InputStream;
+class Error;
+
+class ExpatParser final {
+ const XML_Parser parser;
+
+public:
+ ExpatParser(void *userData)
+ :parser(XML_ParserCreate(nullptr)) {
+ XML_SetUserData(parser, userData);
+ }
+
+ ~ExpatParser() {
+ XML_ParserFree(parser);
+ }
+
+ void SetElementHandler(XML_StartElementHandler start,
+ XML_EndElementHandler end) {
+ XML_SetElementHandler(parser, start, end);
+ }
+
+ void SetCharacterDataHandler(XML_CharacterDataHandler charhndl) {
+ XML_SetCharacterDataHandler(parser, charhndl);
+ }
+
+ bool Parse(const char *data, size_t length, bool is_final,
+ Error &error);
+
+ bool Parse(InputStream &is, Error &error);
+
+ gcc_pure
+ static const char *GetAttribute(const XML_Char **atts,
+ const char *name);
+
+ gcc_pure
+ static const char *GetAttributeCase(const XML_Char **atts,
+ const char *name);
+
+private:
+ void SetError(Error &error);
+};
+
+/**
+ * A specialization of #ExpatParser that provides the most common
+ * callbacks as virtual methods.
+ */
+class CommonExpatParser {
+ ExpatParser parser;
+
+public:
+ CommonExpatParser():parser(this) {
+ parser.SetElementHandler(StartElement, EndElement);
+ parser.SetCharacterDataHandler(CharacterData);
+ }
+
+ bool Parse(const char *data, size_t length, bool is_final,
+ Error &error) {
+ return parser.Parse(data, length, is_final, error);
+ }
+
+ bool Parse(InputStream &is, Error &error) {
+ return parser.Parse(is, error);
+ }
+
+ gcc_pure
+ static const char *GetAttribute(const XML_Char **atts,
+ const char *name) {
+ return ExpatParser::GetAttribute(atts, name);
+ }
+
+ gcc_pure
+ static const char *GetAttributeCase(const XML_Char **atts,
+ const char *name) {
+ return ExpatParser::GetAttributeCase(atts, name);
+ }
+
+protected:
+ virtual void StartElement(const XML_Char *name,
+ const XML_Char **atts) = 0;
+ virtual void EndElement(const XML_Char *name) = 0;
+ virtual void CharacterData(const XML_Char *s, int len) = 0;
+
+private:
+ static void XMLCALL StartElement(void *user_data, const XML_Char *name,
+ const XML_Char **atts) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.StartElement(name, atts);
+ }
+
+ static void XMLCALL EndElement(void *user_data, const XML_Char *name) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.EndElement(name);
+ }
+
+ static void XMLCALL CharacterData(void *user_data,
+ const XML_Char *s, int len) {
+ CommonExpatParser &p = *(CommonExpatParser *)user_data;
+ p.CharacterData(s, len);
+ }
+};
+
+#endif
diff --git a/src/lib/ffmpeg/Domain.cxx b/src/lib/ffmpeg/Domain.cxx
new file mode 100644
index 000000000..78db30bae
--- /dev/null
+++ b/src/lib/ffmpeg/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain ffmpeg_domain("ffmpeg");
diff --git a/src/lib/ffmpeg/Domain.hxx b/src/lib/ffmpeg/Domain.hxx
new file mode 100644
index 000000000..f21498a32
--- /dev/null
+++ b/src/lib/ffmpeg/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_DOMAIN_HXX
+#define MPD_FFMPEG_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain ffmpeg_domain;
+
+#endif
diff --git a/src/lib/ffmpeg/Error.cxx b/src/lib/ffmpeg/Error.cxx
new file mode 100644
index 000000000..bcc12fb1d
--- /dev/null
+++ b/src/lib/ffmpeg/Error.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Error.hxx"
+#include "Domain.hxx"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <libavutil/error.h>
+}
+
+void
+SetFfmpegError(Error &error, int errnum)
+{
+ char msg[256];
+ av_strerror(errnum, msg, sizeof(msg));
+ error.Set(ffmpeg_domain, errnum, msg);
+}
+
+void
+SetFfmpegError(Error &error, int errnum, const char *prefix)
+{
+ char msg[256];
+ av_strerror(errnum, msg, sizeof(msg));
+ error.Format(ffmpeg_domain, errnum, "%s: %s", prefix, msg);
+}
diff --git a/src/lib/ffmpeg/Error.hxx b/src/lib/ffmpeg/Error.hxx
new file mode 100644
index 000000000..943dca6ce
--- /dev/null
+++ b/src/lib/ffmpeg/Error.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FFMPEG_ERROR_HXX
+#define MPD_FFMPEG_ERROR_HXX
+
+class Error;
+
+void
+SetFfmpegError(Error &error, int errnum);
+
+void
+SetFfmpegError(Error &error, int errnum, const char *prefix);
+
+#endif
diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx
new file mode 100644
index 000000000..b8560a4d8
--- /dev/null
+++ b/src/lib/icu/Collate.cxx
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Collate.hxx"
+
+#ifdef HAVE_ICU
+#include "Error.hxx"
+#include "util/WritableBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <unicode/ucol.h>
+#include <unicode/ustring.h>
+#elif defined(HAVE_GLIB)
+#include <glib.h>
+#else
+#include <algorithm>
+#include <ctype.h>
+#endif
+
+#include <assert.h>
+#include <string.h>
+#include <strings.h>
+
+#ifdef HAVE_ICU
+static UCollator *collator;
+#endif
+
+#ifdef HAVE_ICU
+
+bool
+IcuCollateInit(Error &error)
+{
+ assert(collator == nullptr);
+ assert(!error.IsDefined());
+
+ UErrorCode code = U_ZERO_ERROR;
+ collator = ucol_open("", &code);
+ if (collator == nullptr) {
+ error.Format(icu_domain, int(code),
+ "ucol_open() failed: %s", u_errorName(code));
+ return false;
+ }
+
+ return true;
+}
+
+void
+IcuCollateFinish()
+{
+ assert(collator != nullptr);
+
+ ucol_close(collator);
+}
+
+static WritableBuffer<UChar>
+UCharFromUTF8(const char *src)
+{
+ assert(src != nullptr);
+
+ const size_t src_length = strlen(src);
+ const size_t dest_capacity = src_length;
+ UChar *dest = new UChar[dest_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ int32_t dest_length;
+ u_strFromUTF8(dest, dest_capacity, &dest_length,
+ src, src_length,
+ &error_code);
+ if (U_FAILURE(error_code)) {
+ delete[] dest;
+ return nullptr;
+ }
+
+ return { dest, size_t(dest_length) };
+}
+
+static WritableBuffer<char>
+UCharToUTF8(ConstBuffer<UChar> src)
+{
+ assert(!src.IsNull());
+
+ /* worst-case estimate */
+ size_t dest_capacity = 4 * src.size;
+
+ char *dest = new char[dest_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ int32_t dest_length;
+ u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size,
+ &error_code);
+ if (U_FAILURE(error_code)) {
+ delete[] dest;
+ return nullptr;
+ }
+
+ return { dest, size_t(dest_length) };
+}
+
+#endif
+
+gcc_pure
+int
+IcuCollate(const char *a, const char *b)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+#ifdef HAVE_ICU
+ assert(collator != nullptr);
+
+#if U_ICU_VERSION_MAJOR_NUM >= 50
+ UErrorCode code = U_ZERO_ERROR;
+ return (int)ucol_strcollUTF8(collator, a, -1, b, -1, &code);
+#else
+ /* fall back to ucol_strcoll() */
+
+ const auto au = UCharFromUTF8(a);
+ const auto bu = UCharFromUTF8(b);
+
+ int result = !au.IsNull() && !bu.IsNull()
+ ? (int)ucol_strcoll(collator, au.data, au.size,
+ bu.data, bu.size)
+ : strcasecmp(a, b);
+
+ delete[] au.data;
+ delete[] bu.data;
+
+ return result;
+#endif
+
+#elif defined(HAVE_GLIB)
+ return g_utf8_collate(a, b);
+#else
+ return strcasecmp(a, b);
+#endif
+}
+
+std::string
+IcuCaseFold(const char *src)
+{
+#ifdef HAVE_ICU
+ assert(collator != nullptr);
+ assert(src != nullptr);
+
+ const auto u = UCharFromUTF8(src);
+ if (u.IsNull())
+ return std::string(src);
+
+ size_t folded_capacity = u.size * 2u;
+ UChar *folded = new UChar[folded_capacity];
+
+ UErrorCode error_code = U_ZERO_ERROR;
+ size_t folded_length = u_strFoldCase(folded, folded_capacity,
+ u.data, u.size,
+ U_FOLD_CASE_DEFAULT,
+ &error_code);
+ delete[] u.data;
+ if (folded_length == 0 || error_code != U_ZERO_ERROR) {
+ delete[] folded;
+ return std::string(src);
+ }
+
+ auto result2 = UCharToUTF8({folded, folded_length});
+ delete[] folded;
+ if (result2.IsNull())
+ return std::string(src);
+
+ std::string result(result2.data, result2.size);
+ delete[] result2.data;
+#elif defined(HAVE_GLIB)
+ char *tmp = g_utf8_casefold(src, -1);
+ std::string result(tmp);
+ g_free(tmp);
+#else
+ std::string result(src);
+ std::transform(result.begin(), result.end(), result.begin(), tolower);
+#endif
+ return result;
+}
+
diff --git a/src/lib/icu/Collate.hxx b/src/lib/icu/Collate.hxx
new file mode 100644
index 000000000..8ae8de46a
--- /dev/null
+++ b/src/lib/icu/Collate.hxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICU_COLLATE_HXX
+#define MPD_ICU_COLLATE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <string>
+
+class Error;
+
+bool
+IcuCollateInit(Error &error);
+
+void
+IcuCollateFinish();
+
+gcc_pure gcc_nonnull_all
+int
+IcuCollate(const char *a, const char *b);
+
+gcc_pure gcc_nonnull_all
+std::string
+IcuCaseFold(const char *src);
+
+#endif
diff --git a/src/lib/icu/Error.cxx b/src/lib/icu/Error.cxx
new file mode 100644
index 000000000..1fef078ac
--- /dev/null
+++ b/src/lib/icu/Error.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Error.hxx"
+#include "util/Domain.hxx"
+
+const Domain icu_domain("icu");
diff --git a/src/lib/icu/Error.hxx b/src/lib/icu/Error.hxx
new file mode 100644
index 000000000..e96667f57
--- /dev/null
+++ b/src/lib/icu/Error.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICU_ERROR_HXX
+#define MPD_ICU_ERROR_HXX
+
+#include "check.h"
+
+class Domain;
+
+extern const Domain icu_domain;
+
+#endif
diff --git a/src/lib/icu/Init.cxx b/src/lib/icu/Init.cxx
new file mode 100644
index 000000000..1d0ad0777
--- /dev/null
+++ b/src/lib/icu/Init.cxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Error.hxx"
+#include "Collate.hxx"
+#include "util/Error.hxx"
+
+#include <unicode/uclean.h>
+
+bool
+IcuInit(Error &error)
+{
+ UErrorCode code = U_ZERO_ERROR;
+ u_init(&code);
+ if (U_FAILURE(code)) {
+ error.Format(icu_domain, int(code),
+ "u_init() failed: %s", u_errorName(code));
+ return false;
+ }
+
+ return IcuCollateInit(error);
+}
+
+void
+IcuFinish()
+{
+ IcuCollateFinish();
+
+ u_cleanup();
+}
diff --git a/src/lib/icu/Init.hxx b/src/lib/icu/Init.hxx
new file mode 100644
index 000000000..9f585e2bd
--- /dev/null
+++ b/src/lib/icu/Init.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICU_INIT_HXX
+#define MPD_ICU_INIT_HXX
+
+#include "check.h"
+
+class Error;
+
+#ifdef HAVE_ICU
+
+bool
+IcuInit(Error &error);
+
+void
+IcuFinish();
+
+#else
+
+static inline bool IcuInit(Error &) { return true; }
+static inline void IcuFinish() {}
+
+#endif
+
+#endif
diff --git a/src/lib/nfs/Base.cxx b/src/lib/nfs/Base.cxx
new file mode 100644
index 000000000..3004cd11b
--- /dev/null
+++ b/src/lib/nfs/Base.cxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Base.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+static char nfs_base_server[64];
+static char nfs_base_export_name[256];
+static size_t nfs_base_export_name_length;
+
+void
+nfs_set_base(const char *server, const char *export_name)
+{
+ assert(server != nullptr);
+ assert(export_name != nullptr);
+
+ const size_t server_length = strlen(server);
+ const size_t export_name_length = strlen(export_name);
+
+ if (server_length >= sizeof(nfs_base_server) ||
+ export_name_length > sizeof(nfs_base_export_name))
+ return;
+
+ memcpy(nfs_base_server, server, server_length + 1);
+ memcpy(nfs_base_export_name, export_name, export_name_length);
+ nfs_base_export_name_length = export_name_length;
+}
+
+const char *
+nfs_check_base(const char *server, const char *path)
+{
+ assert(server != nullptr);
+ assert(path != nullptr);
+
+ return strcmp(nfs_base_server, server) == 0 &&
+ memcmp(nfs_base_export_name, path,
+ nfs_base_export_name_length) == 0 &&
+ (path[nfs_base_export_name_length] == 0 ||
+ path[nfs_base_export_name_length] == '/')
+ ? path + nfs_base_export_name_length
+ : nullptr;
+}
diff --git a/src/lib/nfs/Base.hxx b/src/lib/nfs/Base.hxx
new file mode 100644
index 000000000..3a92a86d3
--- /dev/null
+++ b/src/lib/nfs/Base.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_BASE_HXX
+#define MPD_NFS_BASE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+/**
+ * Set the "base" NFS server and export name. This will be the
+ * default export that will be mounted if a file within this export is
+ * being opened, instead of guessing the mount point.
+ *
+ * This is a kludge that is not truly thread-safe.
+ */
+void
+nfs_set_base(const char *server, const char *export_name);
+
+/**
+ * Check if the given server and path are inside the "base"
+ * server/export_name. If yes, then a pointer to the portion of
+ * "path" after the export_name is returned; otherwise, nullptr is
+ * returned.
+ */
+gcc_pure
+const char *
+nfs_check_base(const char *server, const char *path);
+
+#endif
diff --git a/src/lib/nfs/Blocking.cxx b/src/lib/nfs/Blocking.cxx
new file mode 100644
index 000000000..58eaf6af2
--- /dev/null
+++ b/src/lib/nfs/Blocking.cxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Blocking.hxx"
+#include "Connection.hxx"
+#include "Domain.hxx"
+#include "event/Call.hxx"
+#include "util/Error.hxx"
+
+bool
+BlockingNfsOperation::Run(Error &_error)
+{
+ /* subscribe to the connection, which will invoke either
+ OnNfsConnectionReady() or OnNfsConnectionFailed() */
+ BlockingCall(connection.GetEventLoop(),
+ [this](){ connection.AddLease(*this); });
+
+ /* wait for completion */
+ if (!LockWaitFinished()) {
+ _error.Set(nfs_domain, 0, "Timeout");
+ return false;
+ }
+
+ /* check for error */
+ if (error.IsDefined()) {
+ _error = std::move(error);
+ return false;
+ }
+
+ return true;
+}
+
+void
+BlockingNfsOperation::OnNfsConnectionReady()
+{
+ if (!Start(error)) {
+ connection.RemoveLease(*this);
+ LockSetFinished();
+ }
+}
+
+void
+BlockingNfsOperation::OnNfsConnectionFailed(const Error &_error)
+{
+ error.Set(_error);
+ LockSetFinished();
+}
+
+void
+BlockingNfsOperation::OnNfsConnectionDisconnected(const Error &_error)
+{
+ error.Set(_error);
+ LockSetFinished();
+}
+
+void
+BlockingNfsOperation::OnNfsCallback(unsigned status, void *data)
+{
+ connection.RemoveLease(*this);
+
+ HandleResult(status, data);
+ LockSetFinished();
+}
+
+void
+BlockingNfsOperation::OnNfsError(Error &&_error)
+{
+ connection.RemoveLease(*this);
+
+ error = std::move(_error);
+ LockSetFinished();
+}
diff --git a/src/lib/nfs/Blocking.hxx b/src/lib/nfs/Blocking.hxx
new file mode 100644
index 000000000..eb16dfb8c
--- /dev/null
+++ b/src/lib/nfs/Blocking.hxx
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BLOCKING_NFS_CALLBACK_HXX
+#define MPD_BLOCKING_NFS_CALLBACK_HXX
+
+#include "check.h"
+#include "Callback.hxx"
+#include "Lease.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "util/Error.hxx"
+
+class NfsConnection;
+
+/**
+ * Utility class to implement a blocking NFS call using the libnfs
+ * async API. The actual method call is deferred to the #EventLoop
+ * thread, and method Run() waits for completion.
+ */
+class BlockingNfsOperation : protected NfsCallback, NfsLease {
+ static constexpr unsigned timeout_ms = 60000;
+
+ Mutex mutex;
+ Cond cond;
+
+ bool finished;
+
+ Error error;
+
+protected:
+ NfsConnection &connection;
+
+public:
+ BlockingNfsOperation(NfsConnection &_connection)
+ :finished(false), connection(_connection) {}
+
+ bool Run(Error &error);
+
+private:
+ bool LockWaitFinished() {
+ const ScopeLock protect(mutex);
+ while (!finished)
+ if (!cond.timed_wait(mutex, timeout_ms))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Mark the operation as "finished" and wake up the waiting
+ * thread.
+ */
+ void LockSetFinished() {
+ const ScopeLock protect(mutex);
+ finished = true;
+ cond.signal();
+ }
+
+ /* virtual methods from NfsLease */
+ void OnNfsConnectionReady() final;
+ void OnNfsConnectionFailed(const Error &error) final;
+ void OnNfsConnectionDisconnected(const Error &error) final;
+
+ /* virtual methods from NfsCallback */
+ void OnNfsCallback(unsigned status, void *data) final;
+ void OnNfsError(Error &&error) final;
+
+protected:
+ virtual bool Start(Error &error) = 0;
+ virtual void HandleResult(unsigned status, void *data) = 0;
+};
+
+#endif
diff --git a/src/lib/nfs/Callback.hxx b/src/lib/nfs/Callback.hxx
new file mode 100644
index 000000000..ae82ecc3c
--- /dev/null
+++ b/src/lib/nfs/Callback.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_CALLBACK_HXX
+#define MPD_NFS_CALLBACK_HXX
+
+#include "check.h"
+
+class Error;
+
+class NfsCallback {
+public:
+ virtual void OnNfsCallback(unsigned status, void *data) = 0;
+ virtual void OnNfsError(Error &&error) = 0;
+};
+
+#endif
diff --git a/src/lib/nfs/Cancellable.hxx b/src/lib/nfs/Cancellable.hxx
new file mode 100644
index 000000000..151be0528
--- /dev/null
+++ b/src/lib/nfs/Cancellable.hxx
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_CANCELLABLE_HXX
+#define MPD_NFS_CANCELLABLE_HXX
+
+#include "Compiler.h"
+
+#include <boost/intrusive/list.hpp>
+
+#include <algorithm>
+
+#include <assert.h>
+
+template<typename T>
+class CancellablePointer
+ : public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
+public:
+ typedef T *pointer_type;
+ typedef T &reference_type;
+ typedef const T &const_reference_type;
+
+private:
+ pointer_type p;
+
+public:
+ explicit CancellablePointer(reference_type _p):p(&_p) {}
+
+ CancellablePointer(const CancellablePointer &) = delete;
+
+ constexpr bool IsCancelled() const {
+ return p == nullptr;
+ }
+
+ void Cancel() {
+ assert(!IsCancelled());
+
+ p = nullptr;
+ }
+
+ reference_type Get() {
+ assert(p != nullptr);
+
+ return *p;
+ }
+
+ constexpr bool Is(const_reference_type other) const {
+ return p == &other;
+ }
+};
+
+template<typename T, typename CT=CancellablePointer<T>>
+class CancellableList {
+public:
+ typedef typename CT::reference_type reference_type;
+ typedef typename CT::const_reference_type const_reference_type;
+
+private:
+ typedef boost::intrusive::list<CT,
+ boost::intrusive::constant_time_size<false>> List;
+ typedef typename List::iterator iterator;
+ typedef typename List::const_iterator const_iterator;
+ List list;
+
+ class MatchPointer {
+ const_reference_type p;
+
+ public:
+ explicit constexpr MatchPointer(const_reference_type _p)
+ :p(_p) {}
+
+ constexpr bool operator()(const CT &a) const {
+ return a.Is(p);
+ }
+ };
+
+ gcc_pure
+ iterator Find(reference_type p) {
+ return std::find_if(list.begin(), list.end(), MatchPointer(p));
+ }
+
+ gcc_pure
+ const_iterator Find(const_reference_type p) const {
+ return std::find_if(list.begin(), list.end(), MatchPointer(p));
+ }
+
+ gcc_pure
+ iterator Find(CT &c) {
+ return list.iterator_to(c);
+ }
+
+ gcc_pure
+ const_iterator Find(const CT &c) const {
+ return list.iterator_to(c);
+ }
+
+public:
+#ifndef NDEBUG
+ gcc_pure
+ bool IsEmpty() const {
+ for (const auto &c : list)
+ if (!c.IsCancelled())
+ return false;
+
+ return true;
+ }
+#endif
+
+ gcc_pure
+ bool Contains(const_reference_type p) const {
+ return Find(p) != list.end();
+ }
+
+ template<typename... Args>
+ CT &Add(reference_type p, Args&&... args) {
+ assert(Find(p) == list.end());
+
+ CT *c = new CT(p, std::forward<Args>(args)...);
+ list.push_back(*c);
+ return *c;
+ }
+
+ void Remove(CT &ct) {
+ auto i = Find(ct);
+ assert(i != list.end());
+
+ list.erase(i);
+ delete &ct;
+ }
+
+ void Cancel(reference_type p) {
+ auto i = Find(p);
+ assert(i != list.end());
+
+ i->Cancel();
+ }
+
+ CT &Get(reference_type p) {
+ auto i = Find(p);
+ assert(i != list.end());
+
+ return *i;
+ }
+
+ template<typename F>
+ void ForEach(F &&f) {
+ for (CT &i : list)
+ f(i);
+ }
+};
+
+#endif
diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx
new file mode 100644
index 000000000..6e9f77345
--- /dev/null
+++ b/src/lib/nfs/Connection.cxx
@@ -0,0 +1,662 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Connection.hxx"
+#include "Lease.hxx"
+#include "Domain.hxx"
+#include "Callback.hxx"
+#include "event/Loop.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+}
+
+#include <utility>
+
+#include <poll.h> /* for POLLIN, POLLOUT */
+
+static constexpr unsigned NFS_MOUNT_TIMEOUT = 60;
+
+inline bool
+NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
+ const char *path,
+ Error &error)
+{
+ assert(connection.GetEventLoop().IsInside());
+
+ int result = nfs_stat_async(ctx, path, Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_stat_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+NfsConnection::CancellableCallback::OpenDirectory(nfs_context *ctx,
+ const char *path,
+ Error &error)
+{
+ assert(connection.GetEventLoop().IsInside());
+
+ int result = nfs_opendir_async(ctx, path, Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_opendir_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+NfsConnection::CancellableCallback::Open(nfs_context *ctx,
+ const char *path, int flags,
+ Error &error)
+{
+ assert(connection.GetEventLoop().IsInside());
+
+ int result = nfs_open_async(ctx, path, flags,
+ Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_open_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
+ struct nfsfh *fh,
+ Error &error)
+{
+ assert(connection.GetEventLoop().IsInside());
+
+ int result = nfs_fstat_async(ctx, fh, Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_fstat_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
+ uint64_t offset, size_t size,
+ Error &error)
+{
+ assert(connection.GetEventLoop().IsInside());
+
+ int result = nfs_pread_async(ctx, fh, offset, size, Callback, this);
+ if (result < 0) {
+ error.Format(nfs_domain, "nfs_pread_async() failed: %s",
+ nfs_get_error(ctx));
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh)
+{
+ assert(connection.GetEventLoop().IsInside());
+ assert(!open);
+ assert(close_fh == nullptr);
+ assert(fh != nullptr);
+
+ close_fh = fh;
+ Cancel();
+}
+
+inline void
+NfsConnection::CancellableCallback::PrepareDestroyContext()
+{
+ assert(IsCancelled());
+
+ if (close_fh != nullptr) {
+ connection.InternalClose(close_fh);
+ close_fh = nullptr;
+ }
+}
+
+inline void
+NfsConnection::CancellableCallback::Callback(int err, void *data)
+{
+ assert(connection.GetEventLoop().IsInside());
+
+ if (!IsCancelled()) {
+ assert(close_fh == nullptr);
+
+ NfsCallback &cb = Get();
+
+ connection.callbacks.Remove(*this);
+
+ if (err >= 0)
+ cb.OnNfsCallback((unsigned)err, data);
+ else
+ cb.OnNfsError(Error(nfs_domain, err,
+ (const char *)data));
+ } else {
+ if (open) {
+ /* a nfs_open_async() call was cancelled - to
+ avoid a memory leak, close the newly
+ allocated file handle immediately */
+ assert(close_fh == nullptr);
+
+ if (err >= 0) {
+ struct nfsfh *fh = (struct nfsfh *)data;
+ connection.Close(fh);
+ }
+ } else if (close_fh != nullptr)
+ connection.DeferClose(close_fh);
+
+ connection.callbacks.Remove(*this);
+ }
+}
+
+void
+NfsConnection::CancellableCallback::Callback(int err,
+ gcc_unused struct nfs_context *nfs,
+ void *data, void *private_data)
+{
+ CancellableCallback &c = *(CancellableCallback *)private_data;
+ c.Callback(err, data);
+}
+
+static constexpr unsigned
+libnfs_to_events(int i)
+{
+ return ((i & POLLIN) ? SocketMonitor::READ : 0) |
+ ((i & POLLOUT) ? SocketMonitor::WRITE : 0);
+}
+
+static constexpr int
+events_to_libnfs(unsigned i)
+{
+ return ((i & SocketMonitor::READ) ? POLLIN : 0) |
+ ((i & SocketMonitor::WRITE) ? POLLOUT : 0);
+}
+
+NfsConnection::~NfsConnection()
+{
+ assert(GetEventLoop().IsInside());
+ assert(new_leases.empty());
+ assert(active_leases.empty());
+ assert(callbacks.IsEmpty());
+ assert(deferred_close.empty());
+
+ if (context != nullptr)
+ DestroyContext();
+}
+
+void
+NfsConnection::AddLease(NfsLease &lease)
+{
+ assert(GetEventLoop().IsInside());
+
+ new_leases.push_back(&lease);
+
+ DeferredMonitor::Schedule();
+}
+
+void
+NfsConnection::RemoveLease(NfsLease &lease)
+{
+ assert(GetEventLoop().IsInside());
+
+ new_leases.remove(&lease);
+ active_leases.remove(&lease);
+}
+
+bool
+NfsConnection::Stat(const char *path, NfsCallback &callback, Error &error)
+{
+ assert(GetEventLoop().IsInside());
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this, false);
+ if (!c.Stat(context, path, error)) {
+ callbacks.Remove(c);
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
+ Error &error)
+{
+ assert(GetEventLoop().IsInside());
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this, true);
+ if (!c.OpenDirectory(context, path, error)) {
+ callbacks.Remove(c);
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+const struct nfsdirent *
+NfsConnection::ReadDirectory(struct nfsdir *dir)
+{
+ assert(GetEventLoop().IsInside());
+
+ return nfs_readdir(context, dir);
+}
+
+void
+NfsConnection::CloseDirectory(struct nfsdir *dir)
+{
+ assert(GetEventLoop().IsInside());
+
+ return nfs_closedir(context, dir);
+}
+
+bool
+NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
+ Error &error)
+{
+ assert(GetEventLoop().IsInside());
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this, true);
+ if (!c.Open(context, path, flags, error)) {
+ callbacks.Remove(c);
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error)
+{
+ assert(GetEventLoop().IsInside());
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this, false);
+ if (!c.Stat(context, fh, error)) {
+ callbacks.Remove(c);
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size,
+ NfsCallback &callback, Error &error)
+{
+ assert(GetEventLoop().IsInside());
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this, false);
+ if (!c.Read(context, fh, offset, size, error)) {
+ callbacks.Remove(c);
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+void
+NfsConnection::Cancel(NfsCallback &callback)
+{
+ callbacks.Cancel(callback);
+}
+
+static void
+DummyCallback(int, struct nfs_context *, void *, void *)
+{
+}
+
+inline void
+NfsConnection::InternalClose(struct nfsfh *fh)
+{
+ assert(GetEventLoop().IsInside());
+ assert(context != nullptr);
+ assert(fh != nullptr);
+
+ nfs_close_async(context, fh, DummyCallback, nullptr);
+}
+
+void
+NfsConnection::Close(struct nfsfh *fh)
+{
+ assert(GetEventLoop().IsInside());
+
+ InternalClose(fh);
+ ScheduleSocket();
+}
+
+void
+NfsConnection::CancelAndClose(struct nfsfh *fh, NfsCallback &callback)
+{
+ CancellableCallback &cancel = callbacks.Get(callback);
+ cancel.CancelAndScheduleClose(fh);
+}
+
+void
+NfsConnection::DestroyContext()
+{
+ assert(GetEventLoop().IsInside());
+ assert(context != nullptr);
+
+#ifndef NDEBUG
+ assert(!in_destroy);
+ in_destroy = true;
+#endif
+
+ if (!mount_finished) {
+ assert(TimeoutMonitor::IsActive());
+ TimeoutMonitor::Cancel();
+ }
+
+ /* cancel pending DeferredMonitor that was scheduled to notify
+ new leases */
+ DeferredMonitor::Cancel();
+
+ if (SocketMonitor::IsDefined())
+ SocketMonitor::Steal();
+
+ callbacks.ForEach([](CancellableCallback &c){
+ c.PrepareDestroyContext();
+ });
+
+ nfs_destroy_context(context);
+ context = nullptr;
+}
+
+inline void
+NfsConnection::DeferClose(struct nfsfh *fh)
+{
+ assert(GetEventLoop().IsInside());
+ assert(in_event);
+ assert(in_service);
+ assert(context != nullptr);
+ assert(fh != nullptr);
+
+ deferred_close.push_front(fh);
+}
+
+void
+NfsConnection::ScheduleSocket()
+{
+ assert(GetEventLoop().IsInside());
+ assert(context != nullptr);
+
+ if (!SocketMonitor::IsDefined()) {
+ int _fd = nfs_get_fd(context);
+ if (_fd < 0)
+ return;
+
+ fd_set_cloexec(_fd, true);
+ SocketMonitor::Open(_fd);
+ }
+
+ SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
+}
+
+inline int
+NfsConnection::Service(unsigned flags)
+{
+ assert(GetEventLoop().IsInside());
+ assert(context != nullptr);
+
+#ifndef NDEBUG
+ assert(!in_event);
+ in_event = true;
+
+ assert(!in_service);
+ in_service = true;
+#endif
+
+ int result = nfs_service(context, events_to_libnfs(flags));
+
+#ifndef NDEBUG
+ assert(context != nullptr);
+ assert(in_service);
+ in_service = false;
+#endif
+
+ return result;
+}
+
+bool
+NfsConnection::OnSocketReady(unsigned flags)
+{
+ assert(GetEventLoop().IsInside());
+ assert(deferred_close.empty());
+
+ bool closed = false;
+
+ const bool was_mounted = mount_finished;
+ if (!mount_finished)
+ /* until the mount is finished, the NFS client may use
+ various sockets, therefore we unregister and
+ re-register it each time */
+ SocketMonitor::Steal();
+
+ const int result = Service(flags);
+
+ while (!deferred_close.empty()) {
+ InternalClose(deferred_close.front());
+ deferred_close.pop_front();
+ }
+
+ if (!was_mounted && mount_finished) {
+ if (postponed_mount_error.IsDefined()) {
+ DestroyContext();
+ closed = true;
+ BroadcastMountError(std::move(postponed_mount_error));
+ } else if (result == 0)
+ BroadcastMountSuccess();
+ } else if (result < 0) {
+ /* the connection has failed */
+ Error error;
+ error.Format(nfs_domain, "NFS connection has failed: %s",
+ nfs_get_error(context));
+
+ BroadcastError(std::move(error));
+
+ DestroyContext();
+ closed = true;
+ } else if (nfs_get_fd(context) < 0) {
+ /* this happens when rpc_reconnect_requeue() is called
+ after the connection broke, but autoreconnect was
+ disabled - nfs_service() returns 0 */
+ Error error;
+ const char *msg = nfs_get_error(context);
+ if (msg == nullptr)
+ error.Set(nfs_domain, "NFS socket disappeared");
+ else
+ error.Format(nfs_domain,
+ "NFS socket disappeared: %s", msg);
+
+ BroadcastError(std::move(error));
+
+ DestroyContext();
+ closed = true;
+ }
+
+ assert(context == nullptr || nfs_get_fd(context) >= 0);
+
+#ifndef NDEBUG
+ assert(in_event);
+ in_event = false;
+#endif
+
+ if (context != nullptr)
+ ScheduleSocket();
+
+ return !closed;
+}
+
+inline void
+NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs,
+ gcc_unused void *data)
+{
+ assert(GetEventLoop().IsInside());
+ assert(context == nfs);
+
+ mount_finished = true;
+
+ assert(TimeoutMonitor::IsActive() || in_destroy);
+ TimeoutMonitor::Cancel();
+
+ if (status < 0) {
+ postponed_mount_error.Format(nfs_domain, status,
+ "nfs_mount_async() failed: %s",
+ nfs_get_error(context));
+ return;
+ }
+}
+
+void
+NfsConnection::MountCallback(int status, nfs_context *nfs, void *data,
+ void *private_data)
+{
+ NfsConnection *c = (NfsConnection *)private_data;
+
+ c->MountCallback(status, nfs, data);
+}
+
+inline bool
+NfsConnection::MountInternal(Error &error)
+{
+ assert(GetEventLoop().IsInside());
+ assert(context == nullptr);
+
+ context = nfs_init_context();
+ if (context == nullptr) {
+ error.Set(nfs_domain, "nfs_init_context() failed");
+ return false;
+ }
+
+ postponed_mount_error.Clear();
+ mount_finished = false;
+
+ TimeoutMonitor::ScheduleSeconds(NFS_MOUNT_TIMEOUT);
+
+#ifndef NDEBUG
+ in_service = false;
+ in_event = false;
+ in_destroy = false;
+#endif
+
+ if (nfs_mount_async(context, server.c_str(), export_name.c_str(),
+ MountCallback, this) != 0) {
+ error.Format(nfs_domain,
+ "nfs_mount_async() failed: %s",
+ nfs_get_error(context));
+ nfs_destroy_context(context);
+ context = nullptr;
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+void
+NfsConnection::BroadcastMountSuccess()
+{
+ assert(GetEventLoop().IsInside());
+
+ while (!new_leases.empty()) {
+ auto i = new_leases.begin();
+ active_leases.splice(active_leases.end(), new_leases, i);
+ (*i)->OnNfsConnectionReady();
+ }
+}
+
+void
+NfsConnection::BroadcastMountError(Error &&error)
+{
+ assert(GetEventLoop().IsInside());
+
+ while (!new_leases.empty()) {
+ auto l = new_leases.front();
+ new_leases.pop_front();
+ l->OnNfsConnectionFailed(error);
+ }
+
+ OnNfsConnectionError(std::move(error));
+}
+
+void
+NfsConnection::BroadcastError(Error &&error)
+{
+ assert(GetEventLoop().IsInside());
+
+ while (!active_leases.empty()) {
+ auto l = active_leases.front();
+ active_leases.pop_front();
+ l->OnNfsConnectionDisconnected(error);
+ }
+
+ BroadcastMountError(std::move(error));
+}
+
+void
+NfsConnection::OnTimeout()
+{
+ assert(GetEventLoop().IsInside());
+ assert(!mount_finished);
+
+ mount_finished = true;
+ DestroyContext();
+
+ BroadcastMountError(Error(nfs_domain, "Mount timeout"));
+}
+
+void
+NfsConnection::RunDeferred()
+{
+ assert(GetEventLoop().IsInside());
+
+ if (context == nullptr) {
+ Error error;
+ if (!MountInternal(error)) {
+ BroadcastMountError(std::move(error));
+ return;
+ }
+ }
+
+ if (mount_finished)
+ BroadcastMountSuccess();
+}
diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx
new file mode 100644
index 000000000..3969a7e8f
--- /dev/null
+++ b/src/lib/nfs/Connection.hxx
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_CONNECTION_HXX
+#define MPD_NFS_CONNECTION_HXX
+
+#include "Lease.hxx"
+#include "Cancellable.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/TimeoutMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Error.hxx"
+
+#include <boost/intrusive/list.hpp>
+
+#include <string>
+#include <list>
+#include <forward_list>
+
+struct nfs_context;
+struct nfsdir;
+struct nfsdirent;
+class NfsCallback;
+
+/**
+ * An asynchronous connection to a NFS server.
+ */
+class NfsConnection : SocketMonitor, TimeoutMonitor, DeferredMonitor {
+ class CancellableCallback : public CancellablePointer<NfsCallback> {
+ NfsConnection &connection;
+
+ /**
+ * Is this a nfs_open_async() operation? If yes, then
+ * we need to call nfs_close_async() on the new file
+ * handle as soon as the callback is invoked
+ * successfully.
+ */
+ const bool open;
+
+ /**
+ * The file handle scheduled to be closed as soon as
+ * the operation finishes.
+ */
+ struct nfsfh *close_fh;
+
+ public:
+ explicit CancellableCallback(NfsCallback &_callback,
+ NfsConnection &_connection,
+ bool _open)
+ :CancellablePointer<NfsCallback>(_callback),
+ connection(_connection),
+ open(_open), close_fh(nullptr) {}
+
+ bool Stat(nfs_context *context, const char *path,
+ Error &error);
+ bool OpenDirectory(nfs_context *context, const char *path,
+ Error &error);
+ bool Open(nfs_context *context, const char *path, int flags,
+ Error &error);
+ bool Stat(nfs_context *context, struct nfsfh *fh,
+ Error &error);
+ bool Read(nfs_context *context, struct nfsfh *fh,
+ uint64_t offset, size_t size,
+ Error &error);
+
+ /**
+ * Cancel the operation and schedule a call to
+ * nfs_close_async() with the given file handle.
+ */
+ void CancelAndScheduleClose(struct nfsfh *fh);
+
+ /**
+ * Called by NfsConnection::DestroyContext() right
+ * before nfs_destroy_context(). This object is given
+ * a chance to prepare for the latter.
+ */
+ void PrepareDestroyContext();
+
+ private:
+ static void Callback(int err, struct nfs_context *nfs,
+ void *data, void *private_data);
+ void Callback(int err, void *data);
+ };
+
+ std::string server, export_name;
+
+ nfs_context *context;
+
+ typedef std::list<NfsLease *> LeaseList;
+ LeaseList new_leases, active_leases;
+
+ typedef CancellableList<NfsCallback, CancellableCallback> CallbackList;
+ CallbackList callbacks;
+
+ /**
+ * A list of NFS file handles (struct nfsfh *) which shall be
+ * closed as soon as nfs_service() returns. If we close the
+ * file handle while in nfs_service(), libnfs may crash, and
+ * deferring this call to after nfs_service() avoids this
+ * problem.
+ */
+ std::forward_list<struct nfsfh *> deferred_close;
+
+ Error postponed_mount_error;
+
+#ifndef NDEBUG
+ /**
+ * True when nfs_service() is being called.
+ */
+ bool in_service;
+
+ /**
+ * True when OnSocketReady() is being called. During that,
+ * event updates are omitted.
+ */
+ bool in_event;
+
+ /**
+ * True when DestroyContext() is being called.
+ */
+ bool in_destroy;
+#endif
+
+ bool mount_finished;
+
+public:
+ gcc_nonnull_all
+ NfsConnection(EventLoop &_loop,
+ const char *_server, const char *_export_name)
+ :SocketMonitor(_loop), TimeoutMonitor(_loop),
+ DeferredMonitor(_loop),
+ server(_server), export_name(_export_name),
+ context(nullptr) {}
+
+ /**
+ * Must be run from EventLoop's thread.
+ */
+ ~NfsConnection();
+
+ gcc_pure
+ const char *GetServer() const {
+ return server.c_str();
+ }
+
+ gcc_pure
+ const char *GetExportName() const {
+ return export_name.c_str();
+ }
+
+ EventLoop &GetEventLoop() {
+ return SocketMonitor::GetEventLoop();
+ }
+
+ /**
+ * Ensure that the connection is established. The connection
+ * is kept up while at least one #NfsLease is registered.
+ *
+ * This method is thread-safe. However, #NfsLease's methods
+ * will be invoked from within the #EventLoop's thread.
+ */
+ void AddLease(NfsLease &lease);
+ void RemoveLease(NfsLease &lease);
+
+ bool Stat(const char *path, NfsCallback &callback, Error &error);
+
+ bool OpenDirectory(const char *path, NfsCallback &callback,
+ Error &error);
+ const struct nfsdirent *ReadDirectory(struct nfsdir *dir);
+ void CloseDirectory(struct nfsdir *dir);
+
+ bool Open(const char *path, int flags, NfsCallback &callback,
+ Error &error);
+ bool Stat(struct nfsfh *fh, NfsCallback &callback, Error &error);
+ bool Read(struct nfsfh *fh, uint64_t offset, size_t size,
+ NfsCallback &callback, Error &error);
+ void Cancel(NfsCallback &callback);
+
+ void Close(struct nfsfh *fh);
+ void CancelAndClose(struct nfsfh *fh, NfsCallback &callback);
+
+protected:
+ virtual void OnNfsConnectionError(Error &&error) = 0;
+
+private:
+ void DestroyContext();
+
+ /**
+ * Wrapper for nfs_close_async().
+ */
+ void InternalClose(struct nfsfh *fh);
+
+ /**
+ * Invoke nfs_close_async() after nfs_service() returns.
+ */
+ void DeferClose(struct nfsfh *fh);
+
+ bool MountInternal(Error &error);
+ void BroadcastMountSuccess();
+ void BroadcastMountError(Error &&error);
+ void BroadcastError(Error &&error);
+
+ static void MountCallback(int status, nfs_context *nfs, void *data,
+ void *private_data);
+ void MountCallback(int status, nfs_context *nfs, void *data);
+
+ void ScheduleSocket();
+
+ /**
+ * Wrapper for nfs_service().
+ */
+ int Service(unsigned flags);
+
+ /* virtual methods from SocketMonitor */
+ virtual bool OnSocketReady(unsigned flags) override;
+
+ /* virtual methods from TimeoutMonitor */
+ void OnTimeout() final;
+
+ /* virtual methods from DeferredMonitor */
+ virtual void RunDeferred() override;
+};
+
+#endif
diff --git a/src/lib/nfs/Domain.cxx b/src/lib/nfs/Domain.cxx
new file mode 100644
index 000000000..fefe0dbf3
--- /dev/null
+++ b/src/lib/nfs/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain nfs_domain("nfs");
diff --git a/src/lib/nfs/Domain.hxx b/src/lib/nfs/Domain.hxx
new file mode 100644
index 000000000..6730b92e1
--- /dev/null
+++ b/src/lib/nfs/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_DOMAIN_HXX
+#define MPD_NFS_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain nfs_domain;
+
+#endif
diff --git a/src/lib/nfs/FileReader.cxx b/src/lib/nfs/FileReader.cxx
new file mode 100644
index 000000000..1b80f2c86
--- /dev/null
+++ b/src/lib/nfs/FileReader.cxx
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FileReader.hxx"
+#include "Glue.hxx"
+#include "Base.hxx"
+#include "Connection.hxx"
+#include "Domain.hxx"
+#include "event/Call.hxx"
+#include "IOThread.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <utility>
+
+#include <assert.h>
+#include <string.h>
+#include <fcntl.h>
+
+NfsFileReader::NfsFileReader()
+ :DeferredMonitor(io_thread_get()), state(State::INITIAL)
+{
+}
+
+NfsFileReader::~NfsFileReader()
+{
+ assert(state == State::INITIAL);
+}
+
+void
+NfsFileReader::Close()
+{
+ if (state == State::INITIAL)
+ return;
+
+ if (state == State::DEFER) {
+ state = State::INITIAL;
+ DeferredMonitor::Cancel();
+ return;
+ }
+
+ /* this cancels State::MOUNT */
+ connection->RemoveLease(*this);
+
+ CancelOrClose();
+}
+
+void
+NfsFileReader::CancelOrClose()
+{
+ assert(state != State::INITIAL &&
+ state != State::DEFER);
+
+ if (state == State::IDLE)
+ /* no async operation in progress: can close
+ immediately */
+ connection->Close(fh);
+ else if (state > State::OPEN)
+ /* one async operation in progress: cancel it and
+ defer the nfs_close_async() call */
+ connection->CancelAndClose(fh, *this);
+ else if (state > State::MOUNT)
+ /* we don't have a file handle yet - just cancel the
+ async operation */
+ connection->Cancel(*this);
+
+ state = State::INITIAL;
+}
+
+void
+NfsFileReader::DeferClose()
+{
+ BlockingCall(io_thread_get(), [this](){ Close(); });
+}
+
+bool
+NfsFileReader::Open(const char *uri, Error &error)
+{
+ assert(state == State::INITIAL);
+
+ if (!StringStartsWith(uri, "nfs://")) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return false;
+ }
+
+ uri += 6;
+
+ const char *slash = strchr(uri, '/');
+ if (slash == nullptr) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return false;
+ }
+
+ server = std::string(uri, slash);
+
+ uri = slash;
+
+ const char *new_path = nfs_check_base(server.c_str(), uri);
+ if (new_path != nullptr) {
+ export_name = std::string(uri, new_path);
+ if (*new_path == 0)
+ new_path = "/";
+ path = new_path;
+ } else {
+ slash = strrchr(uri + 1, '/');
+ if (slash == nullptr || slash[1] == 0) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return false;
+ }
+
+ export_name = std::string(uri, slash);
+ path = slash;
+ }
+
+ state = State::DEFER;
+ DeferredMonitor::Schedule();
+ return true;
+}
+
+bool
+NfsFileReader::Read(uint64_t offset, size_t size, Error &error)
+{
+ assert(state == State::IDLE);
+
+ if (!connection->Read(fh, offset, size, *this, error))
+ return false;
+
+ state = State::READ;
+ return true;
+}
+
+void
+NfsFileReader::CancelRead()
+{
+ if (state == State::READ) {
+ connection->Cancel(*this);
+ state = State::IDLE;
+ }
+}
+
+void
+NfsFileReader::OnNfsConnectionReady()
+{
+ assert(state == State::MOUNT);
+
+ Error error;
+ if (!connection->Open(path, O_RDONLY, *this, error)) {
+ OnNfsFileError(std::move(error));
+ return;
+ }
+
+ state = State::OPEN;
+}
+
+void
+NfsFileReader::OnNfsConnectionFailed(const Error &error)
+{
+ assert(state == State::MOUNT);
+
+ state = State::INITIAL;
+
+ Error copy;
+ copy.Set(error);
+ OnNfsFileError(std::move(copy));
+}
+
+void
+NfsFileReader::OnNfsConnectionDisconnected(const Error &error)
+{
+ assert(state > State::MOUNT);
+
+ CancelOrClose();
+
+ Error copy;
+ copy.Set(error);
+ OnNfsFileError(std::move(copy));
+}
+
+inline void
+NfsFileReader::OpenCallback(nfsfh *_fh)
+{
+ assert(state == State::OPEN);
+ assert(connection != nullptr);
+ assert(_fh != nullptr);
+
+ fh = _fh;
+
+ Error error;
+ if (!connection->Stat(fh, *this, error)) {
+ OnNfsFileError(std::move(error));
+ return;
+ }
+
+ state = State::STAT;
+}
+
+inline void
+NfsFileReader::StatCallback(const struct stat *st)
+{
+ assert(state == State::STAT);
+ assert(connection != nullptr);
+ assert(fh != nullptr);
+ assert(st != nullptr);
+
+ if (!S_ISREG(st->st_mode)) {
+ OnNfsFileError(Error(nfs_domain, "Not a regular file"));
+ return;
+ }
+
+ state = State::IDLE;
+
+ OnNfsFileOpen(st->st_size);
+}
+
+void
+NfsFileReader::OnNfsCallback(unsigned status, void *data)
+{
+ switch (state) {
+ case State::INITIAL:
+ case State::DEFER:
+ case State::MOUNT:
+ case State::IDLE:
+ assert(false);
+ gcc_unreachable();
+
+ case State::OPEN:
+ OpenCallback((struct nfsfh *)data);
+ break;
+
+ case State::STAT:
+ StatCallback((const struct stat *)data);
+ break;
+
+ case State::READ:
+ state = State::IDLE;
+ OnNfsFileRead(data, status);
+ break;
+ }
+}
+
+void
+NfsFileReader::OnNfsError(Error &&error)
+{
+ switch (state) {
+ case State::INITIAL:
+ case State::DEFER:
+ case State::MOUNT:
+ case State::IDLE:
+ assert(false);
+ gcc_unreachable();
+
+ case State::OPEN:
+ connection->RemoveLease(*this);
+ state = State::INITIAL;
+ break;
+
+ case State::STAT:
+ connection->RemoveLease(*this);
+ connection->Close(fh);
+ state = State::INITIAL;
+ break;
+
+ case State::READ:
+ state = State::IDLE;
+ break;
+ }
+
+ OnNfsFileError(std::move(error));
+}
+
+void
+NfsFileReader::RunDeferred()
+{
+ assert(state == State::DEFER);
+
+ state = State::MOUNT;
+
+ connection = &nfs_get_connection(server.c_str(), export_name.c_str());
+ connection->AddLease(*this);
+}
diff --git a/src/lib/nfs/FileReader.hxx b/src/lib/nfs/FileReader.hxx
new file mode 100644
index 000000000..1495a2832
--- /dev/null
+++ b/src/lib/nfs/FileReader.hxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_FILE_READER_HXX
+#define MPD_NFS_FILE_READER_HXX
+
+#include "check.h"
+#include "Lease.hxx"
+#include "Callback.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/stat.h>
+
+struct nfsfh;
+class NfsConnection;
+
+class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor {
+ enum class State {
+ INITIAL,
+ DEFER,
+ MOUNT,
+ OPEN,
+ STAT,
+ READ,
+ IDLE,
+ };
+
+ State state;
+
+ std::string server, export_name;
+ const char *path;
+
+ NfsConnection *connection;
+
+ nfsfh *fh;
+
+public:
+ NfsFileReader();
+ ~NfsFileReader();
+
+ void Close();
+ void DeferClose();
+
+ bool Open(const char *uri, Error &error);
+ bool Read(uint64_t offset, size_t size, Error &error);
+ void CancelRead();
+
+ bool IsIdle() const {
+ return state == State::IDLE;
+ }
+
+protected:
+ virtual void OnNfsFileOpen(uint64_t size) = 0;
+ virtual void OnNfsFileRead(const void *data, size_t size) = 0;
+ virtual void OnNfsFileError(Error &&error) = 0;
+
+private:
+ /**
+ * Cancel the current operation, if any. The NfsLease must be
+ * unregistered already.
+ */
+ void CancelOrClose();
+
+ void OpenCallback(nfsfh *_fh);
+ void StatCallback(const struct stat *st);
+
+ /* virtual methods from NfsLease */
+ void OnNfsConnectionReady() final;
+ void OnNfsConnectionFailed(const Error &error) final;
+ void OnNfsConnectionDisconnected(const Error &error) final;
+
+ /* virtual methods from NfsCallback */
+ void OnNfsCallback(unsigned status, void *data) final;
+ void OnNfsError(Error &&error) final;
+
+ /* virtual methods from DeferredMonitor */
+ void RunDeferred() final;
+};
+
+#endif
diff --git a/src/lib/nfs/Glue.cxx b/src/lib/nfs/Glue.cxx
new file mode 100644
index 000000000..6e1e0f99b
--- /dev/null
+++ b/src/lib/nfs/Glue.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Glue.hxx"
+#include "Manager.hxx"
+#include "IOThread.hxx"
+#include "event/Call.hxx"
+#include "util/Manual.hxx"
+
+#include <assert.h>
+
+static Manual<NfsManager> nfs_glue;
+static unsigned in_use;
+
+void
+nfs_init()
+{
+ if (in_use++ > 0)
+ return;
+
+ nfs_glue.Construct(io_thread_get());
+}
+
+void
+nfs_finish()
+{
+ assert(in_use > 0);
+
+ if (--in_use > 0)
+ return;
+
+ BlockingCall(io_thread_get(), [](){ nfs_glue.Destruct(); });
+}
+
+NfsConnection &
+nfs_get_connection(const char *server, const char *export_name)
+{
+ assert(in_use > 0);
+ assert(io_thread_inside());
+
+ return nfs_glue->GetConnection(server, export_name);
+}
diff --git a/src/lib/nfs/Glue.hxx b/src/lib/nfs/Glue.hxx
new file mode 100644
index 000000000..6da8957cb
--- /dev/null
+++ b/src/lib/nfs/Glue.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_GLUE_HXX
+#define MPD_NFS_GLUE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+class NfsConnection;
+
+void
+nfs_init();
+
+void
+nfs_finish();
+
+gcc_pure
+NfsConnection &
+nfs_get_connection(const char *server, const char *export_name);
+
+#endif
diff --git a/src/lib/nfs/Lease.hxx b/src/lib/nfs/Lease.hxx
new file mode 100644
index 000000000..6f88acf53
--- /dev/null
+++ b/src/lib/nfs/Lease.hxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_LEASE_HXX
+#define MPD_NFS_LEASE_HXX
+
+#include "check.h"
+
+class Error;
+
+class NfsLease {
+public:
+ /**
+ * The #NfsConnection has successfully mounted the server's
+ * export and is ready for regular operation.
+ */
+ virtual void OnNfsConnectionReady() = 0;
+
+ /**
+ * The #NfsConnection has failed to mount the server's export.
+ * This is being called instead of OnNfsConnectionReady().
+ */
+ virtual void OnNfsConnectionFailed(const Error &error) = 0;
+
+ /**
+ * The #NfsConnection has failed after OnNfsConnectionReady()
+ * had been called already.
+ */
+ virtual void OnNfsConnectionDisconnected(const Error &error) = 0;
+};
+
+#endif
diff --git a/src/lib/nfs/Manager.cxx b/src/lib/nfs/Manager.cxx
new file mode 100644
index 000000000..6d50cce18
--- /dev/null
+++ b/src/lib/nfs/Manager.cxx
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Manager.hxx"
+#include "event/Loop.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+void
+NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error)
+{
+ FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName());
+
+ /* defer deletion so the caller
+ (i.e. NfsConnection::OnSocketReady()) can still use this
+ object */
+ manager.ScheduleDelete(*this);
+}
+
+inline bool
+NfsManager::Compare::operator()(const LookupKey a,
+ const ManagedConnection &b) const
+{
+ int result = strcmp(a.server, b.GetServer());
+ if (result != 0)
+ return result < 0;
+
+ result = strcmp(a.export_name, b.GetExportName());
+ return result < 0;
+}
+
+inline bool
+NfsManager::Compare::operator()(const ManagedConnection &a,
+ const LookupKey b) const
+{
+ int result = strcmp(a.GetServer(), b.server);
+ if (result != 0)
+ return result < 0;
+
+ result = strcmp(a.GetExportName(), b.export_name);
+ return result < 0;
+}
+
+NfsManager::~NfsManager()
+{
+ assert(GetEventLoop().IsInside());
+
+ CollectGarbage();
+
+ connections.clear_and_dispose([](ManagedConnection *c){
+ delete c;
+ });
+}
+
+NfsConnection &
+NfsManager::GetConnection(const char *server, const char *export_name)
+{
+ assert(server != nullptr);
+ assert(export_name != nullptr);
+ assert(GetEventLoop().IsInside());
+
+ Map::insert_commit_data hint;
+ auto result = connections.insert_check(LookupKey{server, export_name},
+ Compare(), hint);
+ if (result.second) {
+ auto c = new ManagedConnection(*this, GetEventLoop(),
+ server, export_name);
+ connections.insert_commit(*c, hint);
+ return *c;
+ } else {
+ return *result.first;
+ }
+}
+
+void
+NfsManager::CollectGarbage()
+{
+ assert(GetEventLoop().IsInside());
+
+ garbage.clear_and_dispose([](ManagedConnection *c){
+ delete c;
+ });
+}
+
+void
+NfsManager::OnIdle()
+{
+ CollectGarbage();
+}
diff --git a/src/lib/nfs/Manager.hxx b/src/lib/nfs/Manager.hxx
new file mode 100644
index 000000000..130c81aca
--- /dev/null
+++ b/src/lib/nfs/Manager.hxx
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_MANAGER_HXX
+#define MPD_NFS_MANAGER_HXX
+
+#include "check.h"
+#include "Connection.hxx"
+#include "Compiler.h"
+#include "event/IdleMonitor.hxx"
+
+#include <boost/intrusive/set.hpp>
+#include <boost/intrusive/slist.hpp>
+
+/**
+ * A manager for NFS connections. Handles multiple connections to
+ * multiple NFS servers.
+ */
+class NfsManager final : IdleMonitor {
+ struct LookupKey {
+ const char *server;
+ const char *export_name;
+ };
+
+ class ManagedConnection final
+ : public NfsConnection,
+ public boost::intrusive::slist_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
+ public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
+ NfsManager &manager;
+
+ public:
+ ManagedConnection(NfsManager &_manager, EventLoop &_loop,
+ const char *_server,
+ const char *_export_name)
+ :NfsConnection(_loop, _server, _export_name),
+ manager(_manager) {}
+
+ protected:
+ /* virtual methods from NfsConnection */
+ void OnNfsConnectionError(Error &&error) override;
+ };
+
+ struct Compare {
+ gcc_pure
+ bool operator()(const LookupKey a,
+ const ManagedConnection &b) const;
+
+ gcc_pure
+ bool operator()(const ManagedConnection &a,
+ const LookupKey b) const;
+ };
+
+ /**
+ * Maps server and export_name to #ManagedConnection.
+ */
+ typedef boost::intrusive::set<ManagedConnection,
+ boost::intrusive::compare<Compare>,
+ boost::intrusive::constant_time_size<false>> Map;
+
+ Map connections;
+
+ typedef boost::intrusive::slist<ManagedConnection> List;
+
+ /**
+ * A list of "garbage" connection objects. Their destruction
+ * is postponed because they were thrown into the garbage list
+ * when callers on the stack were still using them.
+ */
+ List garbage;
+
+public:
+ NfsManager(EventLoop &_loop)
+ :IdleMonitor(_loop) {}
+
+ /**
+ * Must be run from EventLoop's thread.
+ */
+ ~NfsManager();
+
+ gcc_pure
+ NfsConnection &GetConnection(const char *server,
+ const char *export_name);
+
+private:
+ void ScheduleDelete(ManagedConnection &c) {
+ connections.erase(connections.iterator_to(c));
+ garbage.push_front(c);
+ IdleMonitor::Schedule();
+ }
+
+ /**
+ * Delete all connections on the #garbage list.
+ */
+ void CollectGarbage();
+
+ /* virtual methods from IdleMonitor */
+ void OnIdle() override;
+};
+
+#endif
diff --git a/src/lib/smbclient/Domain.cxx b/src/lib/smbclient/Domain.cxx
new file mode 100644
index 000000000..00f5ee6c1
--- /dev/null
+++ b/src/lib/smbclient/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain smbclient_domain("smbclient");
diff --git a/src/lib/smbclient/Domain.hxx b/src/lib/smbclient/Domain.hxx
new file mode 100644
index 000000000..3b21c4e60
--- /dev/null
+++ b/src/lib/smbclient/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SMBCLIENT_DOMAIN_HXX
+#define MPD_SMBCLIENT_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain smbclient_domain;
+
+#endif
diff --git a/src/lib/smbclient/Init.cxx b/src/lib/smbclient/Init.cxx
new file mode 100644
index 000000000..a7f2da4dd
--- /dev/null
+++ b/src/lib/smbclient/Init.cxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Mutex.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <libsmbclient.h>
+
+#include <string.h>
+
+static void
+mpd_smbc_get_auth_data(gcc_unused const char *srv,
+ gcc_unused const char *shr,
+ char *wg, gcc_unused int wglen,
+ char *un, gcc_unused int unlen,
+ char *pw, gcc_unused int pwlen)
+{
+ // TODO: implement
+ strcpy(wg, "WORKGROUP");
+ strcpy(un, "");
+ strcpy(pw, "");
+}
+
+bool
+SmbclientInit(Error &error)
+{
+ const ScopeLock protect(smbclient_mutex);
+
+ constexpr int debug = 0;
+ if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) {
+ error.SetErrno("smbc_init() failed");
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/lib/smbclient/Init.hxx b/src/lib/smbclient/Init.hxx
new file mode 100644
index 000000000..21014ec8d
--- /dev/null
+++ b/src/lib/smbclient/Init.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SMBCLIENT_INIT_HXX
+#define MPD_SMBCLIENT_INIT_HXX
+
+#include "check.h"
+
+class Error;
+
+/**
+ * Initialize libsmbclient.
+ */
+bool
+SmbclientInit(Error &error);
+
+#endif
diff --git a/src/lib/smbclient/Mutex.cxx b/src/lib/smbclient/Mutex.cxx
new file mode 100644
index 000000000..4dfc5a9d3
--- /dev/null
+++ b/src/lib/smbclient/Mutex.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Mutex.hxx"
+#include "thread/Mutex.hxx"
+
+Mutex smbclient_mutex;
diff --git a/src/lib/smbclient/Mutex.hxx b/src/lib/smbclient/Mutex.hxx
new file mode 100644
index 000000000..dc7372e6e
--- /dev/null
+++ b/src/lib/smbclient/Mutex.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SMBCLIENT_MUTEX_HXX
+#define MPD_SMBCLIENT_MUTEX_HXX
+
+class Mutex;
+
+/**
+ * Since libsmbclient is not thread-safe, this mutex must be locked
+ * during all libsmbclient function calls.
+ */
+extern Mutex smbclient_mutex;
+
+#endif
diff --git a/src/lib/upnp/Action.hxx b/src/lib/upnp/Action.hxx
new file mode 100644
index 000000000..28c88be92
--- /dev/null
+++ b/src/lib/upnp/Action.hxx
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_ACTION_HXX
+#define MPD_UPNP_ACTION_HXX
+
+#include "Compiler.h"
+
+#include <upnp/upnptools.h>
+
+static inline constexpr unsigned
+CountNameValuePairs()
+{
+ return 0;
+}
+
+template<typename... Args>
+static inline constexpr unsigned
+CountNameValuePairs(gcc_unused const char *name, gcc_unused const char *value,
+ Args... args)
+{
+ return 1 + CountNameValuePairs(args...);
+}
+
+/**
+ * A wrapper for UpnpMakeAction() that counts the number of name/value
+ * pairs and adds the nullptr sentinel.
+ */
+template<typename... Args>
+static inline IXML_Document *
+MakeActionHelper(const char *action_name, const char *service_type,
+ Args... args)
+{
+ const unsigned n = CountNameValuePairs(args...);
+ return UpnpMakeAction(action_name, service_type, n,
+ args...,
+ nullptr, nullptr);
+}
+
+#endif
diff --git a/src/lib/upnp/Callback.hxx b/src/lib/upnp/Callback.hxx
new file mode 100644
index 000000000..85daf0a7e
--- /dev/null
+++ b/src/lib/upnp/Callback.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_CALLBACK_HXX
+#define MPD_UPNP_CALLBACK_HXX
+
+#include <upnp/upnp.h>
+
+/**
+ * A class that is supposed to be used for libupnp asynchronous
+ * callbacks.
+ */
+class UpnpCallback {
+public:
+ /**
+ * Pass this value as "cookie" pointer to libupnp asynchronous
+ * functions.
+ */
+ void *GetUpnpCookie() {
+ return this;
+ }
+
+ static UpnpCallback &FromUpnpCookie(void *cookie) {
+ return *(UpnpCallback *)cookie;
+ }
+
+ virtual int Invoke(Upnp_EventType et, void *evp) = 0;
+};
+
+#endif
diff --git a/src/lib/upnp/ClientInit.cxx b/src/lib/upnp/ClientInit.cxx
new file mode 100644
index 000000000..77d9cf03d
--- /dev/null
+++ b/src/lib/upnp/ClientInit.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ClientInit.hxx"
+#include "Init.hxx"
+#include "Callback.hxx"
+#include "Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <upnp/upnptools.h>
+
+static Mutex upnp_client_init_mutex;
+static unsigned upnp_client_ref;
+static UpnpClient_Handle upnp_client_handle;
+
+static int
+UpnpClientCallback(Upnp_EventType et, void *evp, void *cookie)
+{
+ if (cookie == nullptr)
+ /* this is the cookie passed to UpnpRegisterClient();
+ but can this ever happen? Will libupnp ever invoke
+ the registered callback without that cookie? */
+ return UPNP_E_SUCCESS;
+
+ UpnpCallback &callback = UpnpCallback::FromUpnpCookie(cookie);
+ return callback.Invoke(et, evp);
+}
+
+static bool
+DoInit(Error &error)
+{
+ auto code = UpnpRegisterClient(UpnpClientCallback, nullptr,
+ &upnp_client_handle);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpRegisterClient() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error)
+{
+ if (!UpnpGlobalInit(error))
+ return false;
+
+ upnp_client_init_mutex.lock();
+ bool success = upnp_client_ref > 0 || DoInit(error);
+ upnp_client_init_mutex.unlock();
+
+ if (success) {
+ ++upnp_client_ref;
+ handle = upnp_client_handle;
+ } else
+ UpnpGlobalFinish();
+
+ return success;
+}
+
+void
+UpnpClientGlobalFinish()
+{
+ upnp_client_init_mutex.lock();
+
+ assert(upnp_client_ref > 0);
+ if (--upnp_client_ref == 0)
+ UpnpUnRegisterClient(upnp_client_handle);
+
+ upnp_client_init_mutex.unlock();
+
+ UpnpGlobalFinish();
+}
diff --git a/src/lib/upnp/ClientInit.hxx b/src/lib/upnp/ClientInit.hxx
new file mode 100644
index 000000000..645e64ca6
--- /dev/null
+++ b/src/lib/upnp/ClientInit.hxx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_CLIENT_INIT_HXX
+#define MPD_UPNP_CLIENT_INIT_HXX
+
+#include "check.h"
+
+#include <upnp/upnp.h>
+
+class Error;
+
+bool
+UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error);
+
+void
+UpnpClientGlobalFinish();
+
+#endif
diff --git a/src/lib/upnp/ContentDirectoryService.cxx b/src/lib/upnp/ContentDirectoryService.cxx
new file mode 100644
index 000000000..0e5d2d955
--- /dev/null
+++ b/src/lib/upnp/ContentDirectoryService.cxx
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ContentDirectoryService.hxx"
+#include "Domain.hxx"
+#include "Device.hxx"
+#include "ixmlwrap.hxx"
+#include "Util.hxx"
+#include "Action.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+
+ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service)
+ :m_actionURL(uri_apply_base(service.controlURL, device.URLBase)),
+ m_serviceType(service.serviceType),
+ m_deviceId(device.UDN),
+ m_friendlyName(device.friendlyName),
+ m_manufacturer(device.manufacturer),
+ m_modelName(device.modelName),
+ m_rdreqcnt(200)
+{
+ if (!m_modelName.compare("MediaTomb")) {
+ // Readdir by 200 entries is good for most, but MediaTomb likes
+ // them really big. Actually 1000 is better but I don't dare
+ m_rdreqcnt = 500;
+ }
+}
+
+ContentDirectoryService::~ContentDirectoryService()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+bool
+ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl,
+ std::list<std::string> &result,
+ Error &error) const
+{
+ assert(result.empty());
+
+ IXML_Document *request =
+ UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(),
+ 0,
+ nullptr, nullptr);
+ if (request == 0) {
+ error.Set(upnp_domain, "UpnpMakeAction() failed");
+ return false;
+ }
+
+ IXML_Document *response;
+ auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
+ m_serviceType.c_str(),
+ 0 /*devUDN*/, request, &response);
+ ixmlDocument_free(request);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSendAction() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ const char *s = ixmlwrap::getFirstElementValue(response, "SearchCaps");
+ if (s == nullptr || *s == 0) {
+ ixmlDocument_free(response);
+ return true;
+ }
+
+ bool success = true;
+ if (!csvToStrings(s, result)) {
+ error.Set(upnp_domain, "Bad response");
+ success = false;
+ }
+
+ ixmlDocument_free(response);
+ return success;
+}
diff --git a/src/lib/upnp/ContentDirectoryService.hxx b/src/lib/upnp/ContentDirectoryService.hxx
new file mode 100644
index 000000000..0b03df2e7
--- /dev/null
+++ b/src/lib/upnp/ContentDirectoryService.hxx
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _UPNPDIR_HXX_INCLUDED_
+#define _UPNPDIR_HXX_INCLUDED_
+
+#include "Compiler.h"
+
+#include <upnp/upnp.h>
+
+#include <string>
+#include <list>
+
+class Error;
+class UPnPDevice;
+struct UPnPService;
+class UPnPDirContent;
+
+/**
+ * Content Directory Service class.
+ *
+ * This stores identity data from a directory service
+ * and the device it belongs to, and has methods to query
+ * the directory, using libupnp for handling the UPnP protocols.
+ *
+ * Note: m_rdreqcnt: number of entries requested per directory read.
+ * 0 means all entries. The device can still return less entries than
+ * requested, depending on its own limits. In general it's not optimal
+ * becauses it triggers issues, and is sometimes actually slower, e.g. on
+ * a D-Link NAS 327
+ *
+ * The value chosen may affect by the UpnpSetMaxContentLength
+ * (2000*1024) done during initialization, but this should be ample
+ */
+class ContentDirectoryService {
+ std::string m_actionURL;
+ std::string m_serviceType;
+ std::string m_deviceId;
+ std::string m_friendlyName;
+ std::string m_manufacturer;
+ std::string m_modelName;
+
+ int m_rdreqcnt; // Slice size to use when reading
+
+public:
+ /**
+ * Construct by copying data from device and service objects.
+ *
+ * The discovery service does this: use getDirServices()
+ */
+ ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service);
+
+ /** An empty one */
+ ContentDirectoryService() = default;
+
+ ~ContentDirectoryService();
+
+ /** Read a container's children list into dirbuf.
+ *
+ * @param objectId the UPnP object Id for the container. Root has Id "0"
+ * @param[out] dirbuf stores the entries we read.
+ */
+ bool readDir(UpnpClient_Handle handle,
+ const char *objectId, UPnPDirContent &dirbuf,
+ Error &error) const;
+
+ bool readDirSlice(UpnpClient_Handle handle,
+ const char *objectId, unsigned offset,
+ unsigned count, UPnPDirContent& dirbuf,
+ unsigned &didread, unsigned &total,
+ Error &error) const;
+
+ /** Search the content directory service.
+ *
+ * @param objectId the UPnP object Id under which the search
+ * should be done. Not all servers actually support this below
+ * root. Root has Id "0"
+ * @param searchstring an UPnP searchcriteria string. Check the
+ * UPnP document: UPnP-av-ContentDirectory-v1-Service-20020625.pdf
+ * section 2.5.5. Maybe we'll provide an easier way some day...
+ * @param[out] dirbuf stores the entries we read.
+ */
+ bool search(UpnpClient_Handle handle,
+ const char *objectId, const char *searchstring,
+ UPnPDirContent &dirbuf,
+ Error &error) const;
+
+ /** Read metadata for a given node.
+ *
+ * @param objectId the UPnP object Id. Root has Id "0"
+ * @param[out] dirbuf stores the entries we read. At most one entry will be
+ * returned.
+ */
+ bool getMetadata(UpnpClient_Handle handle,
+ const char *objectId, UPnPDirContent &dirbuf,
+ Error &error) const;
+
+ /** Retrieve search capabilities
+ *
+ * @param[out] result an empty vector: no search, or a single '*' element:
+ * any tag can be used in a search, or a list of usable tag names.
+ */
+ bool getSearchCapabilities(UpnpClient_Handle handle,
+ std::list<std::string> &result,
+ Error &error) const;
+
+ gcc_pure
+ std::string GetURI() const {
+ return "upnp://" + m_deviceId + "/" + m_serviceType;
+ }
+
+ /** Retrieve the "friendly name" for this server, useful for display. */
+ const char *getFriendlyName() const {
+ return m_friendlyName.c_str();
+ }
+};
+
+#endif /* _UPNPDIR_HXX_INCLUDED_ */
diff --git a/src/lib/upnp/Device.cxx b/src/lib/upnp/Device.cxx
new file mode 100644
index 000000000..26bffd0f0
--- /dev/null
+++ b/src/lib/upnp/Device.cxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Device.hxx"
+#include "Util.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "util/Error.hxx"
+
+#include <stdlib.h>
+
+#include <string.h>
+
+UPnPDevice::~UPnPDevice()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+/**
+ * An XML parser which constructs an UPnP device object from the
+ * device descriptor.
+ */
+class UPnPDeviceParser final : public CommonExpatParser {
+ UPnPDevice &m_device;
+
+ std::string *value;
+
+ UPnPService m_tservice;
+
+public:
+ UPnPDeviceParser(UPnPDevice& device)
+ :m_device(device),
+ value(nullptr) {}
+
+protected:
+ virtual void StartElement(const XML_Char *name, const XML_Char **) {
+ value = nullptr;
+
+ switch (name[0]) {
+ case 'c':
+ if (strcmp(name, "controlURL") == 0)
+ value = &m_tservice.controlURL;
+ break;
+ case 'd':
+ if (strcmp(name, "deviceType") == 0)
+ value = &m_device.deviceType;
+ break;
+ case 'f':
+ if (strcmp(name, "friendlyName") == 0)
+ value = &m_device.friendlyName;
+ break;
+ case 'm':
+ if (strcmp(name, "manufacturer") == 0)
+ value = &m_device.manufacturer;
+ else if (strcmp(name, "modelName") == 0)
+ value = &m_device.modelName;
+ break;
+ case 's':
+ if (strcmp(name, "serviceType") == 0)
+ value = &m_tservice.serviceType;
+ break;
+ case 'U':
+ if (strcmp(name, "UDN") == 0)
+ value = &m_device.UDN;
+ else if (strcmp(name, "URLBase") == 0)
+ value = &m_device.URLBase;
+ break;
+ }
+ }
+
+ virtual void EndElement(const XML_Char *name) {
+ if (value != nullptr) {
+ trimstring(*value);
+ value = nullptr;
+ } else if (!strcmp(name, "service")) {
+ m_device.services.emplace_back(std::move(m_tservice));
+ m_tservice.clear();
+ }
+ }
+
+ virtual void CharacterData(const XML_Char *s, int len) {
+ if (value != nullptr)
+ value->append(s, len);
+ }
+};
+
+bool
+UPnPDevice::Parse(const std::string &url, const char *description,
+ Error &error)
+{
+ {
+ UPnPDeviceParser mparser(*this);
+ if (!mparser.Parse(description, strlen(description),
+ true, error))
+ return false;
+ }
+
+ if (URLBase.empty()) {
+ // The standard says that if the URLBase value is empty, we should use
+ // the url the description was retrieved from. However this is
+ // sometimes something like http://host/desc.xml, sometimes something
+ // like http://host/
+
+ if (url.size() < 8) {
+ // ???
+ URLBase = url;
+ } else {
+ auto hostslash = url.find_first_of("/", 7);
+ if (hostslash == std::string::npos || hostslash == url.size()-1) {
+ URLBase = url;
+ } else {
+ URLBase = path_getfather(url);
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/src/lib/upnp/Device.hxx b/src/lib/upnp/Device.hxx
new file mode 100644
index 000000000..dd7ecac2d
--- /dev/null
+++ b/src/lib/upnp/Device.hxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _UPNPDEV_HXX_INCLUDED_
+#define _UPNPDEV_HXX_INCLUDED_
+
+#include <vector>
+#include <string>
+
+class Error;
+
+/**
+ * UPnP Description phase: interpreting the device description which we
+ * downloaded from the URL obtained by the discovery phase.
+ */
+
+/**
+ * Data holder for a UPnP service, parsed from the XML description
+ * downloaded after discovery yielded its URL.
+ */
+struct UPnPService {
+ // e.g. urn:schemas-upnp-org:service:ConnectionManager:1
+ std::string serviceType;
+ std::string controlURL; // e.g.: /upnp/control/cm
+
+ void clear()
+ {
+ serviceType.clear();
+ controlURL.clear();
+ }
+};
+
+/**
+ * Data holder for a UPnP device, parsed from the XML description obtained
+ * during discovery.
+ * A device may include several services. To be of interest to us,
+ * one of them must be a ContentDirectory.
+ */
+class UPnPDevice {
+public:
+ // e.g. urn:schemas-upnp-org:device:MediaServer:1
+ std::string deviceType;
+ // e.g. MediaTomb
+ std::string friendlyName;
+ // Unique device number. This should match the deviceID in the
+ // discovery message. e.g. uuid:a7bdcd12-e6c1-4c7e-b588-3bbc959eda8d
+ std::string UDN;
+ // Base for all relative URLs. e.g. http://192.168.4.4:49152/
+ std::string URLBase;
+ // Manufacturer: e.g. D-Link, PacketVideo ("manufacturer")
+ std::string manufacturer;
+ // Model name: e.g. MediaTomb, DNS-327L ("modelName")
+ std::string modelName;
+ // Services provided by this device.
+ std::vector<UPnPService> services;
+
+ UPnPDevice() = default;
+ UPnPDevice(const UPnPDevice &) = delete;
+ UPnPDevice(UPnPDevice &&) = default;
+ UPnPDevice &operator=(UPnPDevice &&) = default;
+
+ ~UPnPDevice();
+
+ /** Build device from xml description downloaded from discovery
+ * @param url where the description came from
+ * @param description the xml device description
+ */
+ bool Parse(const std::string &url, const char *description,
+ Error &error);
+};
+
+#endif /* _UPNPDEV_HXX_INCLUDED_ */
diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx
new file mode 100644
index 000000000..1539e1512
--- /dev/null
+++ b/src/lib/upnp/Discovery.cxx
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Discovery.hxx"
+#include "Domain.hxx"
+#include "ContentDirectoryService.hxx"
+#include "system/Clock.hxx"
+#include "Log.hxx"
+
+#include <upnp/upnptools.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+// The service type string we are looking for.
+static constexpr char ContentDirectorySType[] = "urn:schemas-upnp-org:service:ContentDirectory:1";
+
+// We don't include a version in comparisons, as we are satisfied with
+// version 1
+gcc_pure
+static bool
+isCDService(const char *st)
+{
+ constexpr size_t sz = sizeof(ContentDirectorySType) - 3;
+ return memcmp(ContentDirectorySType, st, sz) == 0;
+}
+
+// The type of device we're asking for in search
+static constexpr char MediaServerDType[] = "urn:schemas-upnp-org:device:MediaServer:1";
+
+gcc_pure
+static bool
+isMSDevice(const char *st)
+{
+ constexpr size_t sz = sizeof(MediaServerDType) - 3;
+ return memcmp(MediaServerDType, st, sz) == 0;
+}
+
+static void
+AnnounceFoundUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device)
+{
+ for (const auto &service : device.services)
+ if (isCDService(service.serviceType.c_str()))
+ listener.FoundUPnP(ContentDirectoryService(device,
+ service));
+}
+
+static void
+AnnounceLostUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device)
+{
+ for (const auto &service : device.services)
+ if (isCDService(service.serviceType.c_str()))
+ listener.LostUPnP(ContentDirectoryService(device,
+ service));
+}
+
+inline void
+UPnPDeviceDirectory::LockAdd(ContentDirectoryDescriptor &&d)
+{
+ const ScopeLock protect(mutex);
+
+ for (auto &i : directories) {
+ if (i.id == d.id) {
+ i = std::move(d);
+ return;
+ }
+ }
+
+ directories.emplace_back(std::move(d));
+
+ if (listener != nullptr)
+ AnnounceFoundUPnP(*listener, directories.back().device);
+}
+
+inline void
+UPnPDeviceDirectory::LockRemove(const std::string &id)
+{
+ const ScopeLock protect(mutex);
+
+ for (auto i = directories.begin(), end = directories.end();
+ i != end; ++i) {
+ if (i->id == id) {
+ if (listener != nullptr)
+ AnnounceLostUPnP(*listener, i->device);
+
+ directories.erase(i);
+ break;
+ }
+ }
+}
+
+inline void
+UPnPDeviceDirectory::discoExplorer()
+{
+ for (;;) {
+ DiscoveredTask *tsk = 0;
+ if (!discoveredQueue.take(tsk)) {
+ discoveredQueue.workerExit();
+ return;
+ }
+
+ // Device signals its existence and well-being. Perform the
+ // UPnP "description" phase by downloading and decoding the
+ // description document.
+ char *buf;
+ // LINE_SIZE is defined by libupnp's upnp.h...
+ char contentType[LINE_SIZE];
+ int code = UpnpDownloadUrlItem(tsk->url.c_str(), &buf, contentType);
+ if (code != UPNP_E_SUCCESS) {
+ continue;
+ }
+
+ // Update or insert the device
+ ContentDirectoryDescriptor d(std::move(tsk->deviceId),
+ MonotonicClockS(), tsk->expires);
+
+ {
+ Error error2;
+ bool success = d.Parse(tsk->url, buf, error2);
+ free(buf);
+ if (!success) {
+ delete tsk;
+ LogError(error2);
+ continue;
+ }
+ }
+
+ LockAdd(std::move(d));
+ delete tsk;
+ }
+}
+
+void *
+UPnPDeviceDirectory::discoExplorer(void *ctx)
+{
+ UPnPDeviceDirectory &directory = *(UPnPDeviceDirectory *)ctx;
+ directory.discoExplorer();
+ return (void*)1;
+}
+
+inline int
+UPnPDeviceDirectory::OnAlive(Upnp_Discovery *disco)
+{
+ if (isMSDevice(disco->DeviceType) ||
+ isCDService(disco->ServiceType)) {
+ DiscoveredTask *tp = new DiscoveredTask(disco);
+ if (discoveredQueue.put(tp))
+ return UPNP_E_FINISH;
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+inline int
+UPnPDeviceDirectory::OnByeBye(Upnp_Discovery *disco)
+{
+ if (isMSDevice(disco->DeviceType) ||
+ isCDService(disco->ServiceType)) {
+ // Device signals it is going off.
+ LockRemove(disco->DeviceId);
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+// This gets called for all libupnp asynchronous events, in a libupnp
+// thread context.
+// Example: ContentDirectories appearing and disappearing from the network
+// We queue a task for our worker thread(s)
+int
+UPnPDeviceDirectory::Invoke(Upnp_EventType et, void *evp)
+{
+ switch (et) {
+ case UPNP_DISCOVERY_SEARCH_RESULT:
+ case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
+ {
+ Upnp_Discovery *disco = (Upnp_Discovery *)evp;
+ return OnAlive(disco);
+ }
+
+ case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
+ {
+ Upnp_Discovery *disco = (Upnp_Discovery *)evp;
+ return OnByeBye(disco);
+ }
+
+ default:
+ // Ignore other events for now
+ break;
+ }
+
+ return UPNP_E_SUCCESS;
+}
+
+bool
+UPnPDeviceDirectory::expireDevices(Error &error)
+{
+ const ScopeLock protect(mutex);
+ const unsigned now = MonotonicClockS();
+ bool didsomething = false;
+
+ for (auto it = directories.begin();
+ it != directories.end();) {
+ if (now > it->expires) {
+ it = directories.erase(it);
+ didsomething = true;
+ } else {
+ it++;
+ }
+ }
+
+ if (didsomething)
+ return search(error);
+
+ return true;
+}
+
+UPnPDeviceDirectory::UPnPDeviceDirectory(UpnpClient_Handle _handle,
+ UPnPDiscoveryListener *_listener)
+ :handle(_handle),
+ listener(_listener),
+ discoveredQueue("DiscoveredQueue"),
+ m_searchTimeout(2), m_lastSearch(0)
+{
+}
+
+UPnPDeviceDirectory::~UPnPDeviceDirectory()
+{
+ /* this destructor exists here just so it won't get inlined */
+}
+
+bool
+UPnPDeviceDirectory::Start(Error &error)
+{
+ if (!discoveredQueue.start(1, discoExplorer, this)) {
+ error.Set(upnp_domain, "Discover work queue start failed");
+ return false;
+ }
+
+ return search(error);
+}
+
+bool
+UPnPDeviceDirectory::search(Error &error)
+{
+ const unsigned now = MonotonicClockS();
+ if (now - m_lastSearch < 10)
+ return true;
+ m_lastSearch = now;
+
+ // We search both for device and service just in case.
+ int code = UpnpSearchAsync(handle, m_searchTimeout,
+ ContentDirectorySType, GetUpnpCookie());
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSearchAsync() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ code = UpnpSearchAsync(handle, m_searchTimeout,
+ MediaServerDType, GetUpnpCookie());
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpSearchAsync() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+UPnPDeviceDirectory::getDirServices(std::vector<ContentDirectoryService> &out,
+ Error &error)
+{
+ // Has locking, do it before our own lock
+ if (!expireDevices(error))
+ return false;
+
+ const ScopeLock protect(mutex);
+
+ for (auto dit = directories.begin();
+ dit != directories.end(); dit++) {
+ for (const auto &service : dit->device.services) {
+ if (isCDService(service.serviceType.c_str())) {
+ out.emplace_back(dit->device, service);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+UPnPDeviceDirectory::getServer(const char *friendlyName,
+ ContentDirectoryService &server,
+ Error &error)
+{
+ // Has locking, do it before our own lock
+ if (!expireDevices(error))
+ return false;
+
+ const ScopeLock protect(mutex);
+
+ for (const auto &i : directories) {
+ const auto &device = i.device;
+
+ if (device.friendlyName != friendlyName)
+ continue;
+
+ for (const auto &service : device.services) {
+ if (isCDService(service.serviceType.c_str())) {
+ server = ContentDirectoryService(device,
+ service);
+ return true;
+ }
+ }
+ }
+
+ error.Set(upnp_domain, "Server not found");
+ return false;
+}
diff --git a/src/lib/upnp/Discovery.hxx b/src/lib/upnp/Discovery.hxx
new file mode 100644
index 000000000..767811840
--- /dev/null
+++ b/src/lib/upnp/Discovery.hxx
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _UPNPPDISC_H_X_INCLUDED_
+#define _UPNPPDISC_H_X_INCLUDED_
+
+#include "Callback.hxx"
+#include "Device.hxx"
+#include "WorkQueue.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+#include "Compiler.h"
+
+#include <upnp/upnp.h>
+
+#include <list>
+#include <vector>
+#include <string>
+
+class ContentDirectoryService;
+
+class UPnPDiscoveryListener {
+public:
+ virtual void FoundUPnP(const ContentDirectoryService &service) = 0;
+ virtual void LostUPnP(const ContentDirectoryService &service) = 0;
+};
+
+/**
+ * Manage UPnP discovery and maintain a directory of active devices. Singleton.
+ *
+ * We are only interested in MediaServers with a ContentDirectory service
+ * for now, but this could be made more general, by removing the filtering.
+ */
+class UPnPDeviceDirectory final : UpnpCallback {
+ /**
+ * Each appropriate discovery event (executing in a libupnp thread
+ * context) queues the following task object for processing by the
+ * discovery thread.
+ */
+ struct DiscoveredTask {
+ std::string url;
+ std::string deviceId;
+ unsigned expires; // Seconds valid
+
+ DiscoveredTask(const Upnp_Discovery *disco)
+ :url(disco->Location),
+ deviceId(disco->DeviceId),
+ expires(disco->Expires) {}
+ };
+
+ /**
+ * Descriptor for one device having a Content Directory
+ * service found on the network.
+ */
+ class ContentDirectoryDescriptor {
+ public:
+ std::string id;
+
+ UPnPDevice device;
+
+ /**
+ * The MonotonicClockS() time stamp when this device
+ * expires.
+ */
+ unsigned expires;
+
+ ContentDirectoryDescriptor() = default;
+
+ ContentDirectoryDescriptor(std::string &&_id,
+ unsigned last, int exp)
+ :id(std::move(_id)), expires(last + exp + 20) {}
+
+ bool Parse(const std::string &url, const char *description,
+ Error &_error) {
+ return device.Parse(url, description, _error);
+ }
+ };
+
+ const UpnpClient_Handle handle;
+ UPnPDiscoveryListener *const listener;
+
+ Mutex mutex;
+ std::list<ContentDirectoryDescriptor> directories;
+ WorkQueue<DiscoveredTask *> discoveredQueue;
+
+ /**
+ * The UPnP device search timeout, which should actually be
+ * called delay because it's the base of a random delay that
+ * the devices apply to avoid responding all at the same time.
+ */
+ int m_searchTimeout;
+
+ /**
+ * The MonotonicClockS() time stamp of the last search.
+ */
+ unsigned m_lastSearch;
+
+public:
+ UPnPDeviceDirectory(UpnpClient_Handle _handle,
+ UPnPDiscoveryListener *_listener=nullptr);
+ ~UPnPDeviceDirectory();
+
+ UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete;
+ UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete;
+
+ bool Start(Error &error);
+
+ /** Retrieve the directory services currently seen on the network */
+ bool getDirServices(std::vector<ContentDirectoryService> &, Error &);
+
+ /**
+ * Get server by friendly name.
+ */
+ bool getServer(const char *friendlyName,
+ ContentDirectoryService &server,
+ Error &error);
+
+private:
+ bool search(Error &error);
+
+ /**
+ * Look at the devices and get rid of those which have not
+ * been seen for too long. We do this when listing the top
+ * directory.
+ */
+ bool expireDevices(Error &error);
+
+ void LockAdd(ContentDirectoryDescriptor &&d);
+ void LockRemove(const std::string &id);
+
+ /**
+ * Worker routine for the discovery queue. Get messages about
+ * devices appearing and disappearing, and update the
+ * directory pool accordingly.
+ */
+ static void *discoExplorer(void *);
+ void discoExplorer();
+
+ int OnAlive(Upnp_Discovery *disco);
+ int OnByeBye(Upnp_Discovery *disco);
+ int cluCallBack(Upnp_EventType et, void *evp);
+
+ /* virtual methods from class UpnpCallback */
+ virtual int Invoke(Upnp_EventType et, void *evp) override;
+};
+
+
+#endif /* _UPNPPDISC_H_X_INCLUDED_ */
diff --git a/src/lib/upnp/Domain.cxx b/src/lib/upnp/Domain.cxx
new file mode 100644
index 000000000..010d4c7c2
--- /dev/null
+++ b/src/lib/upnp/Domain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain upnp_domain("upnp");
diff --git a/src/lib/upnp/Domain.hxx b/src/lib/upnp/Domain.hxx
new file mode 100644
index 000000000..ec01ef735
--- /dev/null
+++ b/src/lib/upnp/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_DOMAIN_HXX
+#define MPD_UPNP_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain upnp_domain;
+
+#endif
diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx
new file mode 100644
index 000000000..4fc606de9
--- /dev/null
+++ b/src/lib/upnp/Init.cxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Init.hxx"
+#include "Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+
+#include <upnp/upnp.h>
+#include <upnp/upnptools.h>
+#include <upnp/ixml.h>
+
+static Mutex upnp_init_mutex;
+static unsigned upnp_ref;
+
+static bool
+DoInit(Error &error)
+{
+ auto code = UpnpInit(0, 0);
+ if (code != UPNP_E_SUCCESS) {
+ error.Format(upnp_domain, code,
+ "UpnpInit() failed: %s",
+ UpnpGetErrorMessage(code));
+ return false;
+ }
+
+ UpnpSetMaxContentLength(2000*1024);
+
+ // Servers sometimes make error (e.g.: minidlna returns bad utf-8)
+ ixmlRelaxParser(1);
+
+ return true;
+}
+
+bool
+UpnpGlobalInit(Error &error)
+{
+ const ScopeLock protect(upnp_init_mutex);
+
+ if (upnp_ref == 0 && !DoInit(error))
+ return false;
+
+ ++upnp_ref;
+ return true;
+}
+
+void
+UpnpGlobalFinish()
+{
+ const ScopeLock protect(upnp_init_mutex);
+
+ assert(upnp_ref > 0);
+
+ if (--upnp_ref == 0)
+ UpnpFinish();
+}
diff --git a/src/lib/upnp/Init.hxx b/src/lib/upnp/Init.hxx
new file mode 100644
index 000000000..b23f8e2ab
--- /dev/null
+++ b/src/lib/upnp/Init.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_INIT_HXX
+#define MPD_UPNP_INIT_HXX
+
+#include "check.h"
+
+class Error;
+
+bool
+UpnpGlobalInit(Error &error);
+
+void
+UpnpGlobalFinish();
+
+#endif
diff --git a/src/lib/upnp/Util.cxx b/src/lib/upnp/Util.cxx
new file mode 100644
index 000000000..79cfb111c
--- /dev/null
+++ b/src/lib/upnp/Util.cxx
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Util.hxx"
+
+#include <upnp/ixml.h>
+
+#include <assert.h>
+
+/** Get rid of white space at both ends */
+void
+trimstring(std::string &s, const char *ws)
+{
+ auto pos = s.find_first_not_of(ws);
+ if (pos == std::string::npos) {
+ s.clear();
+ return;
+ }
+ s.replace(0, pos, std::string());
+
+ pos = s.find_last_not_of(ws);
+ if (pos != std::string::npos && pos != s.length()-1)
+ s.replace(pos + 1, std::string::npos, std::string());
+}
+
+static void
+path_catslash(std::string &s)
+{
+ if (s.empty() || s.back() != '/')
+ s += '/';
+}
+
+std::string
+path_getfather(const std::string &s)
+{
+ std::string father = s;
+
+ // ??
+ if (father.empty())
+ return "./";
+
+ if (father.back() == '/') {
+ // Input ends with /. Strip it, handle special case for root
+ if (father.length() == 1)
+ return father;
+ father.erase(father.length()-1);
+ }
+
+ auto slp = father.rfind('/');
+ if (slp == std::string::npos)
+ return "./";
+
+ father.erase(slp);
+ path_catslash(father);
+ return father;
+}
+
+std::list<std::string>
+stringToTokens(const std::string &str,
+ const char *delims, bool skipinit)
+{
+ std::list<std::string> tokens;
+
+ std::string::size_type startPos = 0;
+
+ // Skip initial delims, return empty if this eats all.
+ if (skipinit &&
+ (startPos = str.find_first_not_of(delims, 0)) == std::string::npos)
+ return tokens;
+
+ while (startPos < str.size()) {
+ // Find next delimiter or end of string (end of token)
+ auto pos = str.find_first_of(delims, startPos);
+
+ // Add token to the vector and adjust start
+ if (pos == std::string::npos) {
+ tokens.emplace_back(str, startPos);
+ break;
+ } else if (pos == startPos) {
+ // Dont' push empty tokens after first
+ if (tokens.empty())
+ tokens.emplace_back();
+ startPos = ++pos;
+ } else {
+ tokens.emplace_back(str, startPos, pos - startPos);
+ startPos = ++pos;
+ }
+ }
+
+ return tokens;
+}
+
+template <class T>
+bool
+csvToStrings(const char *s, T &tokens)
+{
+ assert(tokens.empty());
+
+ std::string current;
+
+ while (true) {
+ char ch = *s++;
+ if (ch == 0) {
+ tokens.emplace_back(std::move(current));
+ return true;
+ }
+
+ if (ch == '\\') {
+ ch = *s++;
+ if (ch == 0)
+ return false;
+ } else if (ch == ',') {
+ tokens.emplace_back(std::move(current));
+ current.clear();
+ continue;
+ }
+
+ current.push_back(ch);
+ }
+}
+
+template bool csvToStrings<std::list<std::string>>(const char *, std::list<std::string> &);
diff --git a/src/lib/upnp/Util.hxx b/src/lib/upnp/Util.hxx
new file mode 100644
index 000000000..a59f23521
--- /dev/null
+++ b/src/lib/upnp/Util.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UPNP_UTIL_HXX
+#define MPD_UPNP_UTIL_HXX
+
+#include "Compiler.h"
+
+#include <string>
+#include <list>
+
+void
+trimstring(std::string &s, const char *ws = " \t\n");
+
+std::string
+path_getfather(const std::string &s);
+
+gcc_pure
+std::list<std::string>
+stringToTokens(const std::string &str,
+ const char *delims = "/", bool skipinit = true);
+
+template <class T>
+bool
+csvToStrings(const char *s, T &tokens);
+
+#endif /* _UPNPP_H_X_INCLUDED_ */
diff --git a/src/lib/upnp/WorkQueue.hxx b/src/lib/upnp/WorkQueue.hxx
new file mode 100644
index 000000000..fe8ce53f9
--- /dev/null
+++ b/src/lib/upnp/WorkQueue.hxx
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _WORKQUEUE_H_INCLUDED_
+#define _WORKQUEUE_H_INCLUDED_
+
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+#include <assert.h>
+#include <pthread.h>
+
+#include <string>
+#include <queue>
+
+#define LOGINFO(X)
+#define LOGERR(X)
+
+/**
+ * A WorkQueue manages the synchronisation around a queue of work items,
+ * where a number of client threads queue tasks and a number of worker
+ * threads take and execute them. The goal is to introduce some level
+ * of parallelism between the successive steps of a previously single
+ * threaded pipeline. For example data extraction / data preparation / index
+ * update, but this could have other uses.
+ *
+ * There is no individual task status return. In case of fatal error,
+ * the client or worker sets an end condition on the queue. A second
+ * queue could conceivably be used for returning individual task
+ * status.
+ */
+template <class T>
+class WorkQueue {
+ // Configuration
+ const std::string name;
+
+ // Status
+ // Worker threads having called exit
+ unsigned n_workers_exited;
+ bool ok;
+
+ unsigned n_threads;
+ pthread_t *threads;
+
+ // Synchronization
+ std::queue<T> queue;
+ Cond client_cond;
+ Cond worker_cond;
+ Mutex mutex;
+
+public:
+ /** Create a WorkQueue
+ * @param name for message printing
+ * @param hi number of tasks on queue before clients blocks. Default 0
+ * meaning no limit. hi == -1 means that the queue is disabled.
+ * @param lo minimum count of tasks before worker starts. Default 1.
+ */
+ WorkQueue(const char *_name)
+ :name(_name),
+ n_workers_exited(0),
+ ok(false),
+ n_threads(0), threads(nullptr)
+ {
+ }
+
+ ~WorkQueue() {
+ setTerminateAndWait();
+ }
+
+ /** Start the worker threads.
+ *
+ * @param nworkers number of threads copies to start.
+ * @param start_routine thread function. It should loop
+ * taking (QueueWorker::take()) and executing tasks.
+ * @param arg initial parameter to thread function.
+ * @return true if ok.
+ */
+ bool start(unsigned nworkers, void *(*workproc)(void *), void *arg)
+ {
+ const ScopeLock protect(mutex);
+
+ assert(nworkers > 0);
+ assert(!ok);
+ assert(n_threads == 0);
+ assert(threads == nullptr);
+
+ n_threads = nworkers;
+ threads = new pthread_t[n_threads];
+
+ for (unsigned i = 0; i < nworkers; i++) {
+ int err;
+ if ((err = pthread_create(&threads[i], 0, workproc, arg))) {
+ LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n",
+ name.c_str(), err));
+ return false;
+ }
+ }
+
+ ok = true;
+ return true;
+ }
+
+ /** Add item to work queue, called from client.
+ *
+ * Sleeps if there are already too many.
+ */
+ template<typename U>
+ bool put(U &&u)
+ {
+ const ScopeLock protect(mutex);
+
+ queue.emplace(std::forward<U>(u));
+
+ // Just wake one worker, there is only one new task.
+ worker_cond.signal();
+
+ return true;
+ }
+
+
+ /** Tell the workers to exit, and wait for them.
+ */
+ void setTerminateAndWait()
+ {
+ const ScopeLock protect(mutex);
+
+ // Wait for all worker threads to have called workerExit()
+ ok = false;
+ while (n_workers_exited < n_threads) {
+ worker_cond.broadcast();
+ client_cond.wait(mutex);
+ }
+
+ // Perform the thread joins and compute overall status
+ // Workers return (void*)1 if ok
+ for (unsigned i = 0; i < n_threads; ++i) {
+ void *status;
+ pthread_join(threads[i], &status);
+ }
+
+ delete[] threads;
+ threads = nullptr;
+ n_threads = 0;
+
+ // Reset to start state.
+ n_workers_exited = 0;
+ }
+
+ /** Take task from queue. Called from worker.
+ *
+ * Sleeps if there are not enough. Signal if we go to sleep on empty
+ * queue: client may be waiting for our going idle.
+ */
+ bool take(T &tp)
+ {
+ const ScopeLock protect(mutex);
+
+ if (!ok)
+ return false;
+
+ while (queue.empty()) {
+ worker_cond.wait(mutex);
+ if (!ok)
+ return false;
+ }
+
+ tp = std::move(queue.front());
+ queue.pop();
+ return true;
+ }
+
+ /** Advertise exit and abort queue. Called from worker
+ *
+ * This would happen after an unrecoverable error, or when
+ * the queue is terminated by the client. Workers never exit normally,
+ * except when the queue is shut down (at which point ok is set to
+ * false by the shutdown code anyway). The thread must return/exit
+ * immediately after calling this.
+ */
+ void workerExit()
+ {
+ const ScopeLock protect(mutex);
+
+ n_workers_exited++;
+ ok = false;
+ client_cond.broadcast();
+ }
+};
+
+#endif /* _WORKQUEUE_H_INCLUDED_ */
diff --git a/src/lib/upnp/ixmlwrap.cxx b/src/lib/upnp/ixmlwrap.cxx
new file mode 100644
index 000000000..6a2829cf9
--- /dev/null
+++ b/src/lib/upnp/ixmlwrap.cxx
@@ -0,0 +1,44 @@
+/* Copyright (C) 2013 J.F.Dockes
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "ixmlwrap.hxx"
+
+namespace ixmlwrap {
+
+const char *
+getFirstElementValue(IXML_Document *doc, const char *name)
+{
+ const char *ret = nullptr;
+ IXML_NodeList *nodes =
+ ixmlDocument_getElementsByTagName(doc, name);
+
+ if (nodes) {
+ IXML_Node *first = ixmlNodeList_item(nodes, 0);
+ if (first) {
+ IXML_Node *dnode = ixmlNode_getFirstChild(first);
+ if (dnode) {
+ ret = ixmlNode_getNodeValue(dnode);
+ }
+ }
+
+ ixmlNodeList_free(nodes);
+ }
+
+ return ret;
+}
+
+}
diff --git a/src/lib/upnp/ixmlwrap.hxx b/src/lib/upnp/ixmlwrap.hxx
new file mode 100644
index 000000000..0d519a323
--- /dev/null
+++ b/src/lib/upnp/ixmlwrap.hxx
@@ -0,0 +1,35 @@
+/* Copyright (C) 2013 J.F.Dockes
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#ifndef _IXMLWRAP_H_INCLUDED_
+#define _IXMLWRAP_H_INCLUDED_
+
+#include <upnp/ixml.h>
+
+#include <string>
+
+namespace ixmlwrap {
+ /**
+ * Retrieve the text content for the first element of given
+ * name. Returns nullptr if the element does not
+ * contain a text node
+ */
+ const char *getFirstElementValue(IXML_Document *doc,
+ const char *name);
+
+};
+
+#endif /* _IXMLWRAP_H_INCLUDED_ */
diff --git a/src/lib/zlib/Domain.cxx b/src/lib/zlib/Domain.cxx
new file mode 100644
index 000000000..96aad1350
--- /dev/null
+++ b/src/lib/zlib/Domain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain zlib_domain("zlib");
diff --git a/src/lib/zlib/Domain.hxx b/src/lib/zlib/Domain.hxx
new file mode 100644
index 000000000..653ac0209
--- /dev/null
+++ b/src/lib/zlib/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ZLIB_DOMAIN_HXX
+#define MPD_ZLIB_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain zlib_domain;
+
+#endif
diff --git a/src/ls.cxx b/src/ls.cxx
index b1a636416..96c9f60e5 100644
--- a/src/ls.cxx
+++ b/src/ls.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,14 +19,11 @@
#include "config.h"
#include "ls.hxx"
+#include "util/StringUtil.hxx"
#include "util/UriUtil.hxx"
-#include "Client.hxx"
-
-#include <glib.h>
+#include "client/Client.hxx"
#include <assert.h>
-#include <string.h>
-
/**
* file:// is not included in remoteUrlPrefixes, the connection method
@@ -52,12 +49,21 @@ static const char *remoteUrlPrefixes[] = {
"rtmpt://",
"rtmps://",
#endif
+#ifdef ENABLE_SMBCLIENT
+ "smb://",
+#endif
+#ifdef ENABLE_NFS
+ "nfs://",
+#endif
#ifdef ENABLE_CDIO_PARANOIA
"cdda://",
#endif
#ifdef ENABLE_DESPOTIFY
"spt://",
#endif
+#ifdef HAVE_ALSA
+ "alsa://",
+#endif
NULL
};
@@ -92,7 +98,7 @@ bool uri_supported_scheme(const char *uri)
assert(uri_has_scheme(uri));
while (*urlPrefixes) {
- if (g_str_has_prefix(uri, *urlPrefixes))
+ if (StringStartsWith(uri, *urlPrefixes))
return true;
urlPrefixes++;
}
diff --git a/src/ls.hxx b/src/ls.hxx
index 3879563ee..f4b9be967 100644
--- a/src/ls.hxx
+++ b/src/ls.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,8 @@
#ifndef MPD_LS_HXX
#define MPD_LS_HXX
+#include "Compiler.h"
+
#include <stdio.h>
class Client;
@@ -29,6 +31,7 @@ class Client;
* It is not allowed to pass an URI without a scheme, check with
* uri_has_scheme() first.
*/
+gcc_pure
bool uri_supported_scheme(const char *url);
/**
diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx
deleted file mode 100644
index 4a4ca433c..000000000
--- a/src/mixer/AlsaMixerPlugin.cxx
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
-#include "GlobalEvents.hxx"
-#include "Main.hxx"
-#include "event/MultiSocketMonitor.hxx"
-#include "event/Loop.hxx"
-#include "event/Call.hxx"
-#include "util/ASCII.hxx"
-#include "util/ReusableArray.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <algorithm>
-
-#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 *mixer;
-
- ReusableArray<pollfd> pfd_buffer;
-
-public:
- AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
- :MultiSocketMonitor(_loop), mixer(_mixer) {
-#ifdef USE_EPOLL
- _loop.AddCall([this](){ InvalidateSockets(); });
-#else
- _loop.AddIdle(InitAlsaMixerMonitor, this);
-#endif
- }
-
-private:
-#ifndef USE_EPOLL
- static gboolean InitAlsaMixerMonitor(gpointer data) {
- AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data;
- amm.InvalidateSockets();
- return false;
- }
-#endif
-
- virtual int PrepareSockets() 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(Error &error);
- bool Open(Error &error);
- void Close();
-
- int GetVolume(Error &error);
- bool SetVolume(unsigned volume, Error &error);
-};
-
-static constexpr Domain alsa_mixer_domain("alsa_mixer");
-
-int
-AlsaMixerMonitor::PrepareSockets()
-{
- if (mixer == nullptr)
- return -1;
-
- int count = snd_mixer_poll_descriptors_count(mixer);
- if (count < 0)
- count = 0;
-
- struct pollfd *pfds = pfd_buffer.Get(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);
-
- return -1;
-}
-
-void
-AlsaMixerMonitor::DispatchSockets()
-{
- assert(mixer != nullptr);
-
- int err = snd_mixer_handle_events(mixer);
- if (err < 0) {
- FormatError(alsa_mixer_domain,
- "snd_mixer_handle_events() failed: %s",
- snd_strerror(err));
-
- if (err == -ENODEV) {
- /* the sound device was unplugged; disable
- this GSource */
- mixer = nullptr;
- InvalidateSockets();
- return;
- }
- }
-}
-
-/*
- * libasound callbacks
- *
- */
-
-static int
-alsa_mixer_elem_callback(gcc_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(gcc_unused void *ao, const config_param &param,
- gcc_unused Error &error)
-{
- 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();
-}
-
-gcc_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 != nullptr; elem = snd_mixer_elem_next(elem)) {
- if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
- StringEqualsCaseASCII(snd_mixer_selem_get_name(elem),
- name) &&
- snd_mixer_selem_get_index(elem) == idx)
- return elem;
- }
-
- return nullptr;
-}
-
-inline bool
-AlsaMixer::Setup(Error &error)
-{
- int err;
-
- if ((err = snd_mixer_attach(handle, device)) < 0) {
- error.Format(alsa_mixer_domain, err,
- "failed to attach to %s: %s",
- device, snd_strerror(err));
- return false;
- }
-
- if ((err = snd_mixer_selem_register(handle, nullptr,
- nullptr)) < 0) {
- error.Format(alsa_mixer_domain, err,
- "snd_mixer_selem_register() failed: %s",
- snd_strerror(err));
- return false;
- }
-
- if ((err = snd_mixer_load(handle)) < 0) {
- error.Format(alsa_mixer_domain, err,
- "snd_mixer_load() failed: %s\n",
- snd_strerror(err));
- return false;
- }
-
- elem = alsa_mixer_lookup_elem(handle, control, index);
- if (elem == nullptr) {
- error.Format(alsa_mixer_domain, 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(Error &error)
-{
- int err;
-
- volume_set = -1;
-
- err = snd_mixer_open(&handle, 0);
- if (err < 0) {
- error.Format(alsa_mixer_domain, err,
- "snd_mixer_open() failed: %s", snd_strerror(err));
- return false;
- }
-
- if (!Setup(error)) {
- snd_mixer_close(handle);
- return false;
- }
-
- return true;
-}
-
-static bool
-alsa_mixer_open(Mixer *data, Error &error)
-{
- AlsaMixer *am = (AlsaMixer *)data;
-
- return am->Open(error);
-}
-
-inline void
-AlsaMixer::Close()
-{
- assert(handle != nullptr);
-
- delete monitor;
-
- snd_mixer_elem_set_callback(elem, nullptr);
- snd_mixer_close(handle);
-}
-
-static void
-alsa_mixer_close(Mixer *data)
-{
- AlsaMixer *am = (AlsaMixer *)data;
- am->Close();
-}
-
-inline int
-AlsaMixer::GetVolume(Error &error)
-{
- int err;
- int ret;
- long level;
-
- assert(handle != nullptr);
-
- err = snd_mixer_handle_events(handle);
- if (err < 0) {
- error.Format(alsa_mixer_domain, 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) {
- error.Format(alsa_mixer_domain, 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, Error &error)
-{
- AlsaMixer *am = (AlsaMixer *)mixer;
- return am->GetVolume(error);
-}
-
-inline bool
-AlsaMixer::SetVolume(unsigned volume, Error &error)
-{
- float vol;
- long level;
- int err;
-
- assert(handle != nullptr);
-
- 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) {
- error.Format(alsa_mixer_domain, err,
- "failed to set ALSA volume: %s",
- snd_strerror(err));
- return false;
- }
-
- return true;
-}
-
-static bool
-alsa_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error)
-{
- AlsaMixer *am = (AlsaMixer *)mixer;
- return am->SetVolume(volume, error);
-}
-
-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/Listener.hxx b/src/mixer/Listener.hxx
new file mode 100644
index 000000000..6f48fbd4d
--- /dev/null
+++ b/src/mixer/Listener.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MIXER_LISTENER_HXX
+#define MPD_MIXER_LISTENER_HXX
+
+class Mixer;
+
+/**
+ * An interface that listens on events from mixer plugins. The
+ * methods must be thread-safe and non-blocking.
+ */
+class MixerListener {
+public:
+ virtual void OnMixerVolumeChanged(Mixer &mixer, int volume) = 0;
+};
+
+#endif
diff --git a/src/mixer/MixerAll.cxx b/src/mixer/MixerAll.cxx
new file mode 100644
index 000000000..5fef6a92f
--- /dev/null
+++ b/src/mixer/MixerAll.cxx
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "output/MultipleOutputs.hxx"
+#include "MixerControl.hxx"
+#include "MixerInternal.hxx"
+#include "MixerList.hxx"
+#include "output/Internal.hxx"
+#include "pcm/Volume.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+static constexpr Domain mixer_domain("mixer");
+
+static int
+output_mixer_get_volume(const AudioOutput &ao)
+{
+ if (!ao.enabled)
+ return -1;
+
+ Mixer *mixer = ao.mixer;
+ if (mixer == nullptr)
+ return -1;
+
+ Error error;
+ int volume = mixer_get_volume(mixer, error);
+ if (volume < 0 && error.IsDefined())
+ FormatError(error,
+ "Failed to read mixer for '%s'",
+ ao.name);
+
+ return volume;
+}
+
+int
+MultipleOutputs::GetVolume() const
+{
+ unsigned ok = 0;
+ int total = 0;
+
+ for (auto ao : outputs) {
+ int volume = output_mixer_get_volume(*ao);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+static bool
+output_mixer_set_volume(AudioOutput &ao, unsigned volume)
+{
+ assert(volume <= 100);
+
+ if (!ao.enabled)
+ return false;
+
+ Mixer *mixer = ao.mixer;
+ if (mixer == nullptr)
+ return false;
+
+ Error error;
+ bool success = mixer_set_volume(mixer, volume, error);
+ if (!success && error.IsDefined())
+ FormatError(error,
+ "Failed to set mixer for '%s'",
+ ao.name);
+
+ return success;
+}
+
+bool
+MultipleOutputs::SetVolume(unsigned volume)
+{
+ assert(volume <= 100);
+
+ bool success = false;
+ for (auto ao : outputs)
+ success = output_mixer_set_volume(*ao, volume)
+ || success;
+
+ return success;
+}
+
+static int
+output_mixer_get_software_volume(const AudioOutput &ao)
+{
+ if (!ao.enabled)
+ return -1;
+
+ Mixer *mixer = ao.mixer;
+ if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin))
+ return -1;
+
+ return mixer_get_volume(mixer, IgnoreError());
+}
+
+int
+MultipleOutputs::GetSoftwareVolume() const
+{
+ unsigned ok = 0;
+ int total = 0;
+
+ for (auto ao : outputs) {
+ int volume = output_mixer_get_software_volume(*ao);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+void
+MultipleOutputs::SetSoftwareVolume(unsigned volume)
+{
+ assert(volume <= PCM_VOLUME_1);
+
+ for (auto ao : outputs) {
+ const auto mixer = ao->mixer;
+
+ if (mixer != nullptr &&
+ &mixer->plugin == &software_mixer_plugin)
+ mixer_set_volume(mixer, volume, IgnoreError());
+ }
+}
diff --git a/src/mixer/MixerControl.cxx b/src/mixer/MixerControl.cxx
new file mode 100644
index 000000000..6d08140db
--- /dev/null
+++ b/src/mixer/MixerControl.cxx
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MixerControl.hxx"
+#include "MixerInternal.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+Mixer *
+mixer_new(EventLoop &event_loop,
+ const MixerPlugin &plugin, AudioOutput &ao,
+ MixerListener &listener,
+ const config_param &param,
+ Error &error)
+{
+ Mixer *mixer = plugin.init(event_loop, ao, listener, param, error);
+
+ assert(mixer == nullptr || mixer->IsPlugin(plugin));
+
+ return mixer;
+}
+
+void
+mixer_free(Mixer *mixer)
+{
+ assert(mixer != nullptr);
+
+ /* mixers with the "global" flag set might still be open at
+ this point (see mixer_auto_close()) */
+ mixer_close(mixer);
+
+ delete mixer;
+}
+
+bool
+mixer_open(Mixer *mixer, Error &error)
+{
+ bool success;
+
+ assert(mixer != nullptr);
+
+ const ScopeLock protect(mixer->mutex);
+
+ success = mixer->open || (mixer->open = mixer->Open(error));
+
+ mixer->failed = !success;
+
+ return success;
+}
+
+static void
+mixer_close_internal(Mixer *mixer)
+{
+ assert(mixer != nullptr);
+ assert(mixer->open);
+
+ mixer->Close();
+ mixer->open = false;
+}
+
+void
+mixer_close(Mixer *mixer)
+{
+ assert(mixer != nullptr);
+
+ 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, Error &error)
+{
+ int volume;
+
+ assert(mixer != nullptr);
+
+ if (mixer->plugin.global && !mixer->failed &&
+ !mixer_open(mixer, error))
+ return -1;
+
+ const ScopeLock protect(mixer->mutex);
+
+ if (mixer->open) {
+ volume = mixer->GetVolume(error);
+ if (volume < 0 && error.IsDefined())
+ mixer_failed(mixer);
+ } else
+ volume = -1;
+
+ return volume;
+}
+
+bool
+mixer_set_volume(Mixer *mixer, unsigned volume, Error &error)
+{
+ assert(mixer != nullptr);
+ assert(volume <= 100);
+
+ if (mixer->plugin.global && !mixer->failed &&
+ !mixer_open(mixer, error))
+ return false;
+
+ const ScopeLock protect(mixer->mutex);
+
+ return mixer->open && mixer->SetVolume(volume, error);
+}
diff --git a/src/mixer/MixerControl.hxx b/src/mixer/MixerControl.hxx
new file mode 100644
index 000000000..75255d98c
--- /dev/null
+++ b/src/mixer/MixerControl.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Functions which manipulate a #mixer object.
+ */
+
+#ifndef MPD_MIXER_CONTROL_HXX
+#define MPD_MIXER_CONTROL_HXX
+
+class Error;
+class Mixer;
+class EventLoop;
+struct AudioOutput;
+struct MixerPlugin;
+class MixerListener;
+struct config_param;
+
+Mixer *
+mixer_new(EventLoop &event_loop, const MixerPlugin &plugin, AudioOutput &ao,
+ MixerListener &listener,
+ const config_param &param,
+ Error &error);
+
+void
+mixer_free(Mixer *mixer);
+
+bool
+mixer_open(Mixer *mixer, Error &error);
+
+void
+mixer_close(Mixer *mixer);
+
+/**
+ * Close the mixer unless the plugin's "global" flag is set. This is
+ * called when the #AudioOutput is closed.
+ */
+void
+mixer_auto_close(Mixer *mixer);
+
+int
+mixer_get_volume(Mixer *mixer, Error &error);
+
+bool
+mixer_set_volume(Mixer *mixer, unsigned volume, Error &error);
+
+#endif
diff --git a/src/mixer/MixerInternal.hxx b/src/mixer/MixerInternal.hxx
new file mode 100644
index 000000000..7b2cf2b32
--- /dev/null
+++ b/src/mixer/MixerInternal.hxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MIXER_INTERNAL_HXX
+#define MPD_MIXER_INTERNAL_HXX
+
+#include "MixerPlugin.hxx"
+#include "MixerList.hxx"
+#include "thread/Mutex.hxx"
+#include "Compiler.h"
+
+class MixerListener;
+
+class Mixer {
+public:
+ const MixerPlugin &plugin;
+
+ MixerListener &listener;
+
+ /**
+ * 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:
+ explicit Mixer(const MixerPlugin &_plugin, MixerListener &_listener)
+ :plugin(_plugin), listener(_listener),
+ open(false),
+ failed(false) {}
+
+ Mixer(const Mixer &) = delete;
+
+ virtual ~Mixer() {}
+
+ bool IsPlugin(const MixerPlugin &other) const {
+ return &plugin == &other;
+ }
+
+ /**
+ * Open mixer device
+ *
+ * @return true on success, false on error
+ */
+ virtual bool Open(Error &error) = 0;
+
+ /**
+ * Close mixer device
+ */
+ virtual void Close() = 0;
+
+ /**
+ * Reads the current volume.
+ *
+ * @return the current volume (0..100 including) or -1 if
+ * unavailable or on error (error set, mixer will be closed)
+ */
+ gcc_pure
+ virtual int GetVolume(Error &error) = 0;
+
+ /**
+ * Sets the volume.
+ *
+ * @param volume the new volume (0..100 including) @return
+ * true on success, false on error
+ */
+ virtual bool SetVolume(unsigned volume, Error &error) = 0;
+};
+
+#endif
diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx
new file mode 100644
index 000000000..e75b2e6ff
--- /dev/null
+++ b/src/mixer/MixerList.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This header provides "extern" declarations for all mixer plugins.
+ */
+
+#ifndef MPD_MIXER_LIST_HXX
+#define MPD_MIXER_LIST_HXX
+
+struct MixerPlugin;
+
+extern const MixerPlugin software_mixer_plugin;
+extern const MixerPlugin alsa_mixer_plugin;
+extern const MixerPlugin oss_mixer_plugin;
+extern const MixerPlugin roar_mixer_plugin;
+extern const MixerPlugin pulse_mixer_plugin;
+extern const MixerPlugin winmm_mixer_plugin;
+
+#endif
diff --git a/src/mixer/MixerPlugin.hxx b/src/mixer/MixerPlugin.hxx
new file mode 100644
index 000000000..02bae844e
--- /dev/null
+++ b/src/mixer/MixerPlugin.hxx
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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
+
+struct config_param;
+struct AudioOutput;
+class Mixer;
+class MixerListener;
+class EventLoop;
+class Error;
+
+struct MixerPlugin {
+ /**
+ * Alocates and configures a mixer device.
+ *
+ * @param ao the associated AudioOutput
+ * @param param the configuration section
+ * @param error_r location to store the error occurring, or
+ * nullptr to ignore errors
+ * @return a mixer object, or nullptr on error
+ */
+ Mixer *(*init)(EventLoop &event_loop, AudioOutput &ao,
+ MixerListener &listener,
+ const config_param &param,
+ Error &error);
+
+ /**
+ * 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/MixerType.cxx b/src/mixer/MixerType.cxx
new file mode 100644
index 000000000..cd45db0d9
--- /dev/null
+++ b/src/mixer/MixerType.cxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "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/mixer/MixerType.hxx b/src/mixer/MixerType.hxx
new file mode 100644
index 000000000..bfa2637b7
--- /dev/null
+++ b/src/mixer/MixerType.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx
deleted file mode 100644
index 0a459bc97..000000000
--- a/src/mixer/OssMixerPlugin.cxx
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
-#include "system/fd_util.h"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#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, Error &error);
- bool Open(Error &error);
- void Close();
-
- int GetVolume(Error &error);
- bool SetVolume(unsigned volume, Error &error);
-};
-
-static constexpr Domain oss_mixer_domain("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 (StringEqualsCaseASCII(name, labels[i], name_length) &&
- (labels[i][name_length] == 0 ||
- labels[i][name_length] == ' '))
- return i;
- }
- return -1;
-}
-
-inline bool
-OssMixer::Configure(const config_param &param, Error &error)
-{
- 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) {
- error.Format(oss_mixer_domain, 0,
- "no such mixer control: %s", control);
- return false;
- }
- } else
- volume_control = SOUND_MIXER_PCM;
-
- return true;
-}
-
-static Mixer *
-oss_mixer_init(gcc_unused void *ao, const config_param &param,
- Error &error)
-{
- OssMixer *om = new OssMixer();
-
- if (!om->Configure(param, error)) {
- 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(Error &error)
-{
- device_fd = open_cloexec(device, O_RDONLY, 0);
- if (device_fd < 0) {
- error.FormatErrno("failed to open %s", device);
- return false;
- }
-
- if (control) {
- int devmask = 0;
-
- if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- error.SetErrno("READ_DEVMASK failed");
- Close();
- return false;
- }
-
- if (((1 << volume_control) & devmask) == 0) {
- error.Format(oss_mixer_domain, 0,
- "mixer control \"%s\" not usable",
- control);
- Close();
- return false;
- }
- }
-
- return true;
-}
-
-static bool
-oss_mixer_open(Mixer *data, Error &error)
-{
- OssMixer *om = (OssMixer *) data;
-
- return om->Open(error);
-}
-
-inline int
-OssMixer::GetVolume(Error &error)
-{
- int left, right, level;
- int ret;
-
- assert(device_fd >= 0);
-
- ret = ioctl(device_fd, MIXER_READ(volume_control), &level);
- if (ret < 0) {
- error.SetErrno("failed to read OSS volume");
- return false;
- }
-
- left = level & 0xff;
- right = (level & 0xff00) >> 8;
-
- if (left != right) {
- FormatWarning(oss_mixer_domain,
- "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, Error &error)
-{
- OssMixer *om = (OssMixer *)mixer;
- return om->GetVolume(error);
-}
-
-inline bool
-OssMixer::SetVolume(unsigned volume, Error &error)
-{
- 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) {
- error.SetErrno("failed to set OSS volume");
- return false;
- }
-
- return true;
-}
-
-static bool
-oss_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error)
-{
- OssMixer *om = (OssMixer *)mixer;
- return om->SetVolume(volume, error);
-}
-
-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
deleted file mode 100644
index ff10256cb..000000000
--- a/src/mixer/PulseMixerPlugin.cxx
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PulseMixerPlugin.hxx"
-#include "MixerInternal.hxx"
-#include "output/PulseOutputPlugin.hxx"
-#include "GlobalEvents.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#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>
-
-struct PulseMixer final : public Mixer {
- PulseOutput *output;
-
- bool online;
- struct pa_cvolume volume;
-
- PulseMixer(PulseOutput *_output)
- :Mixer(pulse_mixer_plugin),
- output(_output), online(false)
- {
- }
-};
-
-static constexpr Domain pulse_mixer_domain("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(gcc_unused pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
- PulseMixer *pm = (PulseMixer *)userdata;
-
- if (eol)
- return;
-
- if (i == nullptr) {
- 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 != nullptr);
- assert(stream != nullptr);
- 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 == nullptr) {
- FormatError(pulse_mixer_domain,
- "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(gcc_unused PulseMixer *pm,
- struct pa_context *context)
-{
- pa_operation *o;
-
- assert(context != nullptr);
-
- o = pa_context_subscribe(context,
- (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
- nullptr, nullptr);
- if (o == nullptr) {
- FormatError(pulse_mixer_domain,
- "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,
- Error &error)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- if (ao == nullptr) {
- error.Set(pulse_mixer_domain,
- "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, gcc_unused Error &error)
-{
- 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, Error &error)
-{
- PulseMixer *pm = (PulseMixer *) mixer;
- struct pa_cvolume cvolume;
- bool success;
-
- pulse_output_lock(pm->output);
-
- if (!pm->online) {
- pulse_output_unlock(pm->output);
- error.Set(pulse_mixer_domain, "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);
- 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
deleted file mode 100644
index fa73e0f5e..000000000
--- a/src/mixer/PulseMixerPlugin.hxx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PULSE_MIXER_PLUGIN_HXX
-#define MPD_PULSE_MIXER_PLUGIN_HXX
-
-#include <pulse/def.h>
-
-struct PulseMixer;
-struct pa_context;
-struct pa_stream;
-
-void
-pulse_mixer_on_connect(PulseMixer *pm, struct pa_context *context);
-
-void
-pulse_mixer_on_disconnect(PulseMixer *pm);
-
-void
-pulse_mixer_on_change(PulseMixer *pm,
- struct pa_context *context, struct pa_stream *stream);
-
-#endif
diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx
deleted file mode 100644
index 6bd700551..000000000
--- a/src/mixer/RoarMixerPlugin.cxx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 Error &error)
-{
- 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 Error &error)
-{
- RoarMixer *self = (RoarMixer *)mixer;
- return roar_output_get_volume(self->self);
-}
-
-static bool
-roar_mixer_set_volume(Mixer *mixer, unsigned volume,
- gcc_unused Error &error)
-{
- 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
deleted file mode 100644
index 193e68f23..000000000
--- a/src/mixer/SoftwareMixerPlugin.cxx
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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 "util/Error.hxx"
-
-#include <assert.h>
-#include <math.h>
-
-static Filter *
-CreateVolumeFilter()
-{
- Error error;
- return filter_new(&volume_filter_plugin, config_param(), error);
-}
-
-struct SoftwareMixer final : public Mixer {
- Filter *filter;
-
- /**
- * If this is true, then this object "owns" the #Filter
- * instance (see above). It will be set to false by
- * software_mixer_get_filter(); after that, the caller will be
- * responsible for the #Filter.
- */
- bool owns_filter;
-
- unsigned volume;
-
- SoftwareMixer()
- :Mixer(software_mixer_plugin),
- filter(CreateVolumeFilter()),
- owns_filter(true),
- volume(100)
- {
- assert(filter != nullptr);
- }
-
- ~SoftwareMixer() {
- if (owns_filter)
- delete filter;
- }
-};
-
-static Mixer *
-software_mixer_init(gcc_unused void *ao,
- gcc_unused const config_param &param,
- gcc_unused Error &error)
-{
- 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 Error &error)
-{
- SoftwareMixer *sm = (SoftwareMixer *)mixer;
-
- return sm->volume;
-}
-
-static bool
-software_mixer_set_volume(Mixer *mixer, unsigned volume,
- gcc_unused Error &error)
-{
- 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));
- assert(sm->owns_filter);
-
- sm->owns_filter = false;
- return sm->filter;
-}
diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx
deleted file mode 100644
index be59c08db..000000000
--- a/src/mixer/SoftwareMixerPlugin.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/Volume.cxx b/src/mixer/Volume.cxx
new file mode 100644
index 000000000..abb01fb40
--- /dev/null
+++ b/src/mixer/Volume.cxx
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Volume.hxx"
+#include "output/MultipleOutputs.hxx"
+#include "Idle.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Domain.hxx"
+#include "system/PeriodClock.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#define SW_VOLUME_STATE "sw_volume: "
+
+static constexpr Domain volume_domain("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 PeriodClock hardware_volume_clock;
+
+void
+InvalidateHardwareVolume()
+{
+ /* flush the hardware volume cache */
+ last_hardware_volume = -1;
+}
+
+int
+volume_level_get(const MultipleOutputs &outputs)
+{
+ if (last_hardware_volume >= 0 &&
+ !hardware_volume_clock.CheckUpdate(1000))
+ /* throttle access to hardware mixers */
+ return last_hardware_volume;
+
+ last_hardware_volume = outputs.GetVolume();
+ return last_hardware_volume;
+}
+
+static bool
+software_volume_change(MultipleOutputs &outputs, unsigned volume)
+{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+ outputs.SetSoftwareVolume(volume);
+
+ return true;
+}
+
+static bool
+hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
+{
+ /* reset the cache */
+ last_hardware_volume = -1;
+
+ return outputs.SetVolume(volume);
+}
+
+bool
+volume_level_change(MultipleOutputs &outputs, unsigned volume)
+{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+
+ idle_add(IDLE_MIXER);
+
+ return hardware_volume_change(outputs, volume);
+}
+
+bool
+read_sw_volume_state(const char *line, MultipleOutputs &outputs)
+{
+ char *end = nullptr;
+ long int sv;
+
+ if (!StringStartsWith(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(outputs, sv);
+ else
+ FormatWarning(volume_domain,
+ "Can't parse software volume: %s", line);
+ return true;
+}
+
+void
+save_sw_volume_state(BufferedOutputStream &os)
+{
+ os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
+}
+
+unsigned
+sw_volume_state_get_hash(void)
+{
+ return volume_software_set;
+}
diff --git a/src/mixer/Volume.hxx b/src/mixer/Volume.hxx
new file mode 100644
index 000000000..d787a6415
--- /dev/null
+++ b/src/mixer/Volume.hxx
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_VOLUME_HXX
+#define MPD_VOLUME_HXX
+
+#include "Compiler.h"
+
+class MultipleOutputs;
+class BufferedOutputStream;
+
+void
+InvalidateHardwareVolume();
+
+gcc_pure
+int
+volume_level_get(const MultipleOutputs &outputs);
+
+bool
+volume_level_change(MultipleOutputs &outputs, unsigned volume);
+
+bool
+read_sw_volume_state(const char *line, MultipleOutputs &outputs);
+
+void
+save_sw_volume_state(BufferedOutputStream &os);
+
+/**
+ * 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.
+ */
+gcc_pure
+unsigned
+sw_volume_state_get_hash(void);
+
+#endif
diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx
deleted file mode 100644
index dbb43dce4..000000000
--- a/src/mixer/WinmmMixerPlugin.cxx
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "MixerInternal.hxx"
-#include "OutputAPI.hxx"
-#include "output/WinmmOutputPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <mmsystem.h>
-
-#include <assert.h>
-#include <math.h>
-#include <windows.h>
-
-struct WinmmMixer final : public Mixer {
- WinmmOutput *output;
-
- WinmmMixer(WinmmOutput *_output)
- :Mixer(winmm_mixer_plugin),
- output(_output) {
- }
-};
-
-static constexpr Domain winmm_mixer_domain("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,
- gcc_unused Error &error)
-{
- 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, Error &error)
-{
- WinmmMixer *wm = (WinmmMixer *) mixer;
- DWORD volume;
- HWAVEOUT handle = winmm_output_get_handle(wm->output);
- MMRESULT result = waveOutGetVolume(handle, &volume);
-
- if (result != MMSYSERR_NOERROR) {
- error.Set(winmm_mixer_domain, "Failed to get winmm volume");
- return -1;
- }
-
- return winmm_volume_decode(volume);
-}
-
-static bool
-winmm_mixer_set_volume(Mixer *mixer, unsigned volume, Error &error)
-{
- 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) {
- error.Set(winmm_mixer_domain, "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/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx
new file mode 100644
index 000000000..cd787182a
--- /dev/null
+++ b/src/mixer/plugins/AlsaMixerPlugin.cxx
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mixer/MixerInternal.hxx"
+#include "mixer/Listener.hxx"
+#include "output/OutputAPI.hxx"
+#include "event/MultiSocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "event/Loop.hxx"
+#include "util/ASCII.hxx"
+#include "util/ReusableArray.hxx"
+#include "util/Clamp.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <algorithm>
+
+#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 : MultiSocketMonitor, DeferredMonitor {
+ snd_mixer_t *mixer;
+
+ ReusableArray<pollfd> pfd_buffer;
+
+public:
+ AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
+ :MultiSocketMonitor(_loop), DeferredMonitor(_loop),
+ mixer(_mixer) {
+ DeferredMonitor::Schedule();
+ }
+
+private:
+ virtual void RunDeferred() override {
+ InvalidateSockets();
+ }
+
+ virtual int PrepareSockets() override;
+ virtual void DispatchSockets() override;
+};
+
+class AlsaMixer final : public Mixer {
+ EventLoop &event_loop;
+
+ 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(EventLoop &_event_loop, MixerListener &_listener)
+ :Mixer(alsa_mixer_plugin, _listener),
+ event_loop(_event_loop) {}
+
+ virtual ~AlsaMixer();
+
+ void Configure(const config_param &param);
+ bool Setup(Error &error);
+
+ /* virtual methods from class Mixer */
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual int GetVolume(Error &error) override;
+ virtual bool SetVolume(unsigned volume, Error &error) override;
+};
+
+static constexpr Domain alsa_mixer_domain("alsa_mixer");
+
+int
+AlsaMixerMonitor::PrepareSockets()
+{
+ if (mixer == nullptr) {
+ ClearSocketList();
+ return -1;
+ }
+
+ int count = snd_mixer_poll_descriptors_count(mixer);
+ if (count < 0)
+ count = 0;
+
+ struct pollfd *pfds = pfd_buffer.Get(count);
+
+ count = snd_mixer_poll_descriptors(mixer, pfds, count);
+ if (count < 0)
+ count = 0;
+
+ ReplaceSocketList(pfds, count);
+ return -1;
+}
+
+void
+AlsaMixerMonitor::DispatchSockets()
+{
+ assert(mixer != nullptr);
+
+ int err = snd_mixer_handle_events(mixer);
+ if (err < 0) {
+ FormatError(alsa_mixer_domain,
+ "snd_mixer_handle_events() failed: %s",
+ snd_strerror(err));
+
+ if (err == -ENODEV) {
+ /* the sound device was unplugged; disable
+ this GSource */
+ mixer = nullptr;
+ InvalidateSockets();
+ return;
+ }
+ }
+}
+
+/*
+ * libasound callbacks
+ *
+ */
+
+static int
+alsa_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned mask)
+{
+ AlsaMixer &mixer = *(AlsaMixer *)
+ snd_mixer_elem_get_callback_private(elem);
+
+ if (mask & SND_CTL_EVENT_MASK_VALUE) {
+ int volume = mixer.GetVolume(IgnoreError());
+ mixer.listener.OnMixerVolumeChanged(mixer, volume);
+ }
+
+ 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(EventLoop &event_loop, gcc_unused AudioOutput &ao,
+ MixerListener &listener,
+ const config_param &param,
+ gcc_unused Error &error)
+{
+ AlsaMixer *am = new AlsaMixer(event_loop, listener);
+ am->Configure(param);
+
+ return am;
+}
+
+AlsaMixer::~AlsaMixer()
+{
+ /* free libasound's config cache */
+ snd_config_update_free_global();
+}
+
+gcc_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 != nullptr; elem = snd_mixer_elem_next(elem)) {
+ if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
+ StringEqualsCaseASCII(snd_mixer_selem_get_name(elem),
+ name) &&
+ snd_mixer_selem_get_index(elem) == idx)
+ return elem;
+ }
+
+ return nullptr;
+}
+
+inline bool
+AlsaMixer::Setup(Error &error)
+{
+ int err;
+
+ if ((err = snd_mixer_attach(handle, device)) < 0) {
+ error.Format(alsa_mixer_domain, err,
+ "failed to attach to %s: %s",
+ device, snd_strerror(err));
+ return false;
+ }
+
+ if ((err = snd_mixer_selem_register(handle, nullptr,
+ nullptr)) < 0) {
+ error.Format(alsa_mixer_domain, err,
+ "snd_mixer_selem_register() failed: %s",
+ snd_strerror(err));
+ return false;
+ }
+
+ if ((err = snd_mixer_load(handle)) < 0) {
+ error.Format(alsa_mixer_domain, err,
+ "snd_mixer_load() failed: %s\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ elem = alsa_mixer_lookup_elem(handle, control, index);
+ if (elem == nullptr) {
+ error.Format(alsa_mixer_domain, 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_private(elem, this);
+ snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback);
+
+ monitor = new AlsaMixerMonitor(event_loop, handle);
+
+ return true;
+}
+
+inline bool
+AlsaMixer::Open(Error &error)
+{
+ int err;
+
+ volume_set = -1;
+
+ err = snd_mixer_open(&handle, 0);
+ if (err < 0) {
+ error.Format(alsa_mixer_domain, err,
+ "snd_mixer_open() failed: %s", snd_strerror(err));
+ return false;
+ }
+
+ if (!Setup(error)) {
+ snd_mixer_close(handle);
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+AlsaMixer::Close()
+{
+ assert(handle != nullptr);
+
+ delete monitor;
+
+ snd_mixer_elem_set_callback(elem, nullptr);
+ snd_mixer_close(handle);
+}
+
+inline int
+AlsaMixer::GetVolume(Error &error)
+{
+ int err;
+ int ret;
+ long level;
+
+ assert(handle != nullptr);
+
+ err = snd_mixer_handle_events(handle);
+ if (err < 0) {
+ error.Format(alsa_mixer_domain, 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) {
+ error.Format(alsa_mixer_domain, 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;
+}
+
+inline bool
+AlsaMixer::SetVolume(unsigned volume, Error &error)
+{
+ float vol;
+ long level;
+ int err;
+
+ assert(handle != nullptr);
+
+ vol = volume;
+
+ volume_set = vol + 0.5;
+
+ level = (long)(((vol / 100.0) * (volume_max - volume_min) +
+ volume_min) + 0.5);
+ level = Clamp(level, volume_min, volume_max);
+
+ err = snd_mixer_selem_set_playback_volume_all(elem, level);
+ if (err < 0) {
+ error.Format(alsa_mixer_domain, err,
+ "failed to set ALSA volume: %s",
+ snd_strerror(err));
+ return false;
+ }
+
+ return true;
+}
+
+const MixerPlugin alsa_mixer_plugin = {
+ alsa_mixer_init,
+ true,
+};
diff --git a/src/mixer/plugins/OssMixerPlugin.cxx b/src/mixer/plugins/OssMixerPlugin.cxx
new file mode 100644
index 000000000..6615c7022
--- /dev/null
+++ b/src/mixer/plugins/OssMixerPlugin.cxx
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mixer/MixerInternal.hxx"
+#include "config/ConfigData.hxx"
+#include "system/fd_util.h"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <fcntl.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 final : public Mixer {
+ const char *device;
+ const char *control;
+
+ int device_fd;
+ int volume_control;
+
+public:
+ OssMixer(MixerListener &_listener)
+ :Mixer(oss_mixer_plugin, _listener) {}
+
+ bool Configure(const config_param &param, Error &error);
+
+ /* virtual methods from class Mixer */
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual int GetVolume(Error &error) override;
+ virtual bool SetVolume(unsigned volume, Error &error) override;
+};
+
+static constexpr Domain oss_mixer_domain("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 (StringEqualsCaseASCII(name, labels[i], name_length) &&
+ (labels[i][name_length] == 0 ||
+ labels[i][name_length] == ' '))
+ return i;
+ }
+ return -1;
+}
+
+inline bool
+OssMixer::Configure(const config_param &param, Error &error)
+{
+ 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) {
+ error.Format(oss_mixer_domain, 0,
+ "no such mixer control: %s", control);
+ return false;
+ }
+ } else
+ volume_control = SOUND_MIXER_PCM;
+
+ return true;
+}
+
+static Mixer *
+oss_mixer_init(gcc_unused EventLoop &event_loop, gcc_unused AudioOutput &ao,
+ MixerListener &listener,
+ const config_param &param,
+ Error &error)
+{
+ OssMixer *om = new OssMixer(listener);
+
+ if (!om->Configure(param, error)) {
+ delete om;
+ return nullptr;
+ }
+
+ return om;
+}
+
+void
+OssMixer::Close()
+{
+ assert(device_fd >= 0);
+
+ close(device_fd);
+}
+
+bool
+OssMixer::Open(Error &error)
+{
+ device_fd = open_cloexec(device, O_RDONLY, 0);
+ if (device_fd < 0) {
+ error.FormatErrno("failed to open %s", device);
+ return false;
+ }
+
+ if (control) {
+ int devmask = 0;
+
+ if (ioctl(device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
+ error.SetErrno("READ_DEVMASK failed");
+ Close();
+ return false;
+ }
+
+ if (((1 << volume_control) & devmask) == 0) {
+ error.Format(oss_mixer_domain, 0,
+ "mixer control \"%s\" not usable",
+ control);
+ Close();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int
+OssMixer::GetVolume(Error &error)
+{
+ int left, right, level;
+ int ret;
+
+ assert(device_fd >= 0);
+
+ ret = ioctl(device_fd, MIXER_READ(volume_control), &level);
+ if (ret < 0) {
+ error.SetErrno("failed to read OSS volume");
+ return false;
+ }
+
+ left = level & 0xff;
+ right = (level & 0xff00) >> 8;
+
+ if (left != right) {
+ FormatWarning(oss_mixer_domain,
+ "volume for left and right is not the same, \"%i\" and "
+ "\"%i\"\n", left, right);
+ }
+
+ return left;
+}
+
+bool
+OssMixer::SetVolume(unsigned volume, Error &error)
+{
+ 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) {
+ error.SetErrno("failed to set OSS volume");
+ return false;
+ }
+
+ return true;
+}
+
+const MixerPlugin oss_mixer_plugin = {
+ oss_mixer_init,
+ true,
+};
diff --git a/src/mixer/plugins/PulseMixerPlugin.cxx b/src/mixer/plugins/PulseMixerPlugin.cxx
new file mode 100644
index 000000000..c5f20723b
--- /dev/null
+++ b/src/mixer/plugins/PulseMixerPlugin.cxx
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PulseMixerPlugin.hxx"
+#include "mixer/MixerInternal.hxx"
+#include "mixer/Listener.hxx"
+#include "output/plugins/PulseOutputPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <pulse/context.h>
+#include <pulse/introspect.h>
+#include <pulse/stream.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+
+class PulseMixer final : public Mixer {
+ PulseOutput &output;
+
+ bool online;
+ struct pa_cvolume volume;
+
+public:
+ PulseMixer(PulseOutput &_output, MixerListener &_listener)
+ :Mixer(pulse_mixer_plugin, _listener),
+ output(_output), online(false)
+ {
+ }
+
+ virtual ~PulseMixer();
+
+ void Offline();
+ void VolumeCallback(const pa_sink_input_info *i, int eol);
+ void Update(pa_context *context, pa_stream *stream);
+ int GetVolumeInternal(Error &error);
+
+ /* virtual methods from class Mixer */
+ virtual bool Open(gcc_unused Error &error) override {
+ return true;
+ }
+
+ virtual void Close() override {
+ }
+
+ virtual int GetVolume(Error &error) override;
+ virtual bool SetVolume(unsigned volume, Error &error) override;
+};
+
+static constexpr Domain pulse_mixer_domain("pulse_mixer");
+
+void
+PulseMixer::Offline()
+{
+ if (!online)
+ return;
+
+ online = false;
+
+ listener.OnMixerVolumeChanged(*this, -1);
+}
+
+inline void
+PulseMixer::VolumeCallback(const pa_sink_input_info *i, int eol)
+{
+ if (eol)
+ return;
+
+ if (i == nullptr) {
+ Offline();
+ return;
+ }
+
+ online = true;
+ volume = i->volume;
+
+ listener.OnMixerVolumeChanged(*this, GetVolumeInternal(IgnoreError()));
+}
+
+/**
+ * Callback invoked by pulse_mixer_update(). Receives the new mixer
+ * value.
+ */
+static void
+pulse_mixer_volume_cb(gcc_unused pa_context *context, const pa_sink_input_info *i,
+ int eol, void *userdata)
+{
+ PulseMixer *pm = (PulseMixer *)userdata;
+ pm->VolumeCallback(i, eol);
+}
+
+inline void
+PulseMixer::Update(pa_context *context, pa_stream *stream)
+{
+ assert(context != nullptr);
+ assert(stream != nullptr);
+ assert(pa_stream_get_state(stream) == PA_STREAM_READY);
+
+ pa_operation *o =
+ pa_context_get_sink_input_info(context,
+ pa_stream_get_index(stream),
+ pulse_mixer_volume_cb, this);
+ if (o == nullptr) {
+ FormatError(pulse_mixer_domain,
+ "pa_context_get_sink_input_info() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ Offline();
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_connect(gcc_unused PulseMixer &pm,
+ struct pa_context *context)
+{
+ pa_operation *o;
+
+ assert(context != nullptr);
+
+ o = pa_context_subscribe(context,
+ (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
+ nullptr, nullptr);
+ if (o == nullptr) {
+ FormatError(pulse_mixer_domain,
+ "pa_context_subscribe() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_disconnect(PulseMixer &pm)
+{
+ pm.Offline();
+}
+
+void
+pulse_mixer_on_change(PulseMixer &pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pm.Update(context, stream);
+}
+
+static Mixer *
+pulse_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
+ MixerListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ PulseOutput &po = (PulseOutput &)ao;
+ PulseMixer *pm = new PulseMixer(po, listener);
+
+ pulse_output_set_mixer(po, *pm);
+
+ return pm;
+}
+
+PulseMixer::~PulseMixer()
+{
+ pulse_output_clear_mixer(output, *this);
+}
+
+int
+PulseMixer::GetVolume(gcc_unused Error &error)
+{
+ pulse_output_lock(output);
+
+ int result = GetVolumeInternal(error);
+ pulse_output_unlock(output);
+
+ return result;
+}
+
+/**
+ * Pulse mainloop lock must be held by caller
+ */
+int
+PulseMixer::GetVolumeInternal(gcc_unused Error &error)
+{
+ return online ?
+ (int)((100 * (pa_cvolume_avg(&volume) + 1)) / PA_VOLUME_NORM)
+ : -1;
+}
+
+bool
+PulseMixer::SetVolume(unsigned new_volume, Error &error)
+{
+ pulse_output_lock(output);
+
+ if (!online) {
+ pulse_output_unlock(output);
+ error.Set(pulse_mixer_domain, "disconnected");
+ return false;
+ }
+
+ struct pa_cvolume cvolume;
+ pa_cvolume_set(&cvolume, volume.channels,
+ (pa_volume_t)new_volume * PA_VOLUME_NORM / 100 + 0.5);
+ bool success = pulse_output_set_volume(output, &cvolume, error);
+ if (success)
+ volume = cvolume;
+
+ pulse_output_unlock(output);
+ return success;
+}
+
+const MixerPlugin pulse_mixer_plugin = {
+ pulse_mixer_init,
+ false,
+};
diff --git a/src/mixer/plugins/PulseMixerPlugin.hxx b/src/mixer/plugins/PulseMixerPlugin.hxx
new file mode 100644
index 000000000..9b3a6daf1
--- /dev/null
+++ b/src/mixer/plugins/PulseMixerPlugin.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PULSE_MIXER_PLUGIN_HXX
+#define MPD_PULSE_MIXER_PLUGIN_HXX
+
+class PulseMixer;
+struct pa_context;
+struct pa_stream;
+
+void
+pulse_mixer_on_connect(PulseMixer &pm, pa_context *context);
+
+void
+pulse_mixer_on_disconnect(PulseMixer &pm);
+
+void
+pulse_mixer_on_change(PulseMixer &pm, pa_context *context, pa_stream *stream);
+
+#endif
diff --git a/src/mixer/plugins/RoarMixerPlugin.cxx b/src/mixer/plugins/RoarMixerPlugin.cxx
new file mode 100644
index 000000000..8e198478d
--- /dev/null
+++ b/src/mixer/plugins/RoarMixerPlugin.cxx
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2003-2014 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/MixerInternal.hxx"
+#include "output/plugins/RoarOutputPlugin.hxx"
+#include "Compiler.h"
+
+class RoarMixer final : public Mixer {
+ /** the base mixer class */
+ RoarOutput &self;
+
+public:
+ RoarMixer(RoarOutput &_output, MixerListener &_listener)
+ :Mixer(roar_mixer_plugin, _listener),
+ self(_output) {}
+
+ /* virtual methods from class Mixer */
+ virtual bool Open(gcc_unused Error &error) override {
+ return true;
+ }
+
+ virtual void Close() override {
+ }
+
+ virtual int GetVolume(Error &error) override;
+ virtual bool SetVolume(unsigned volume, Error &error) override;
+};
+
+static Mixer *
+roar_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
+ MixerListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new RoarMixer((RoarOutput &)ao, listener);
+}
+
+int
+RoarMixer::GetVolume(gcc_unused Error &error)
+{
+ return roar_output_get_volume(self);
+}
+
+bool
+RoarMixer::SetVolume(unsigned volume, gcc_unused Error &error)
+{
+ return roar_output_set_volume(self, volume);
+}
+
+const MixerPlugin roar_mixer_plugin = {
+ roar_mixer_init,
+ false,
+};
diff --git a/src/mixer/plugins/SoftwareMixerPlugin.cxx b/src/mixer/plugins/SoftwareMixerPlugin.cxx
new file mode 100644
index 000000000..f14766002
--- /dev/null
+++ b/src/mixer/plugins/SoftwareMixerPlugin.cxx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SoftwareMixerPlugin.hxx"
+#include "mixer/MixerInternal.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/plugins/VolumeFilterPlugin.hxx"
+#include "pcm/Volume.hxx"
+#include "config/ConfigData.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+#include <math.h>
+
+static Filter *
+CreateVolumeFilter()
+{
+ return filter_new(&volume_filter_plugin, config_param(),
+ IgnoreError());
+}
+
+class SoftwareMixer final : public Mixer {
+ Filter *filter;
+
+ /**
+ * If this is true, then this object "owns" the #Filter
+ * instance (see above). It will be set to false by
+ * software_mixer_get_filter(); after that, the caller will be
+ * responsible for the #Filter.
+ */
+ bool owns_filter;
+
+ /**
+ * The current volume in percent (0..100).
+ */
+ unsigned volume;
+
+public:
+ SoftwareMixer(MixerListener &_listener)
+ :Mixer(software_mixer_plugin, _listener),
+ filter(CreateVolumeFilter()),
+ owns_filter(true),
+ volume(100)
+ {
+ assert(filter != nullptr);
+ }
+
+ virtual ~SoftwareMixer() {
+ if (owns_filter)
+ delete filter;
+ }
+
+ Filter *GetFilter();
+
+ /* virtual methods from class Mixer */
+ virtual bool Open(gcc_unused Error &error) override {
+ return true;
+ }
+
+ virtual void Close() override {
+ }
+
+ virtual int GetVolume(gcc_unused Error &error) override {
+ return volume;
+ }
+
+ virtual bool SetVolume(unsigned volume, Error &error) override;
+};
+
+static Mixer *
+software_mixer_init(gcc_unused EventLoop &event_loop,
+ gcc_unused AudioOutput &ao,
+ MixerListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new SoftwareMixer(listener);
+}
+
+gcc_const
+static unsigned
+PercentVolumeToSoftwareVolume(unsigned volume)
+{
+ assert(volume <= 100);
+
+ if (volume >= 100)
+ return PCM_VOLUME_1;
+ else if (volume > 0)
+ return pcm_float_to_volume((exp(volume / 25.0) - 1) /
+ (54.5981500331F - 1));
+ else
+ return 0;
+}
+
+bool
+SoftwareMixer::SetVolume(unsigned new_volume, gcc_unused Error &error)
+{
+ assert(new_volume <= 100);
+
+ volume = new_volume;
+ volume_filter_set(filter, PercentVolumeToSoftwareVolume(new_volume));
+ return true;
+}
+
+const MixerPlugin software_mixer_plugin = {
+ software_mixer_init,
+ true,
+};
+
+inline Filter *
+SoftwareMixer::GetFilter()
+{
+ assert(owns_filter);
+
+ owns_filter = false;
+ return filter;
+}
+
+Filter *
+software_mixer_get_filter(Mixer *mixer)
+{
+ SoftwareMixer *sm = (SoftwareMixer *)mixer;
+ assert(sm->IsPlugin(software_mixer_plugin));
+ return sm->GetFilter();
+}
diff --git a/src/mixer/plugins/SoftwareMixerPlugin.hxx b/src/mixer/plugins/SoftwareMixerPlugin.hxx
new file mode 100644
index 000000000..581d2ac17
--- /dev/null
+++ b/src/mixer/plugins/SoftwareMixerPlugin.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/plugins/WinmmMixerPlugin.cxx b/src/mixer/plugins/WinmmMixerPlugin.cxx
new file mode 100644
index 000000000..e0436011a
--- /dev/null
+++ b/src/mixer/plugins/WinmmMixerPlugin.cxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mixer/MixerInternal.hxx"
+#include "output/OutputAPI.hxx"
+#include "output/plugins/WinmmOutputPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <mmsystem.h>
+
+#include <assert.h>
+#include <math.h>
+#include <windows.h>
+
+class WinmmMixer final : public Mixer {
+ WinmmOutput &output;
+
+public:
+ WinmmMixer(WinmmOutput &_output, MixerListener &_listener)
+ :Mixer(winmm_mixer_plugin, _listener),
+ output(_output) {
+ }
+
+ /* virtual methods from class Mixer */
+ virtual bool Open(gcc_unused Error &error) override {
+ return true;
+ }
+
+ virtual void Close() override {
+ }
+
+ virtual int GetVolume(Error &error) override;
+ virtual bool SetVolume(unsigned volume, Error &error) override;
+};
+
+static constexpr Domain winmm_mixer_domain("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(gcc_unused EventLoop &event_loop, AudioOutput &ao,
+ MixerListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new WinmmMixer((WinmmOutput &)ao, listener);
+}
+
+int
+WinmmMixer::GetVolume(Error &error)
+{
+ DWORD volume;
+ HWAVEOUT handle = winmm_output_get_handle(output);
+ MMRESULT result = waveOutGetVolume(handle, &volume);
+
+ if (result != MMSYSERR_NOERROR) {
+ error.Set(winmm_mixer_domain, "Failed to get winmm volume");
+ return -1;
+ }
+
+ return winmm_volume_decode(volume);
+}
+
+bool
+WinmmMixer::SetVolume(unsigned volume, Error &error)
+{
+ DWORD value = winmm_volume_encode(volume);
+ HWAVEOUT handle = winmm_output_get_handle(output);
+ MMRESULT result = waveOutSetVolume(handle, value);
+
+ if (result != MMSYSERR_NOERROR) {
+ error.Set(winmm_mixer_domain, "Failed to set winmm volume");
+ return false;
+ }
+
+ return true;
+}
+
+const MixerPlugin winmm_mixer_plugin = {
+ winmm_mixer_init,
+ false,
+};
diff --git a/src/neighbor/Explorer.hxx b/src/neighbor/Explorer.hxx
new file mode 100644
index 000000000..84a54840c
--- /dev/null
+++ b/src/neighbor/Explorer.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_EXPLORER_HXX
+#define MPD_NEIGHBOR_EXPLORER_HXX
+
+#include <forward_list>
+
+class Error;
+class NeighborListener;
+struct NeighborInfo;
+
+/**
+ * An object that explores the neighborhood for music servers.
+ *
+ * As soon as this object is opened, it will start exploring, and
+ * notify the #NeighborListener when it found or lost something.
+ *
+ * The implementation is supposed to be non-blocking. This can be
+ * implemented either using the #EventLoop instance that was passed to
+ * the NeighborPlugin or by moving the blocking parts in a dedicated
+ * thread.
+ */
+class NeighborExplorer {
+protected:
+ NeighborListener &listener;
+
+ explicit NeighborExplorer(NeighborListener &_listener)
+ :listener(_listener) {}
+
+public:
+ typedef std::forward_list<NeighborInfo> List;
+
+ /**
+ * Free instance data.
+ */
+ virtual ~NeighborExplorer() {}
+
+ /**
+ * Start exploring the neighborhood.
+ */
+ virtual bool Open(Error &error) = 0;
+
+ /**
+ * Stop exploring.
+ */
+ virtual void Close() = 0;
+
+ /**
+ * Obtain a list of currently known neighbors.
+ */
+ virtual List GetList() const = 0;
+};
+
+#endif
diff --git a/src/neighbor/Glue.cxx b/src/neighbor/Glue.cxx
new file mode 100644
index 000000000..fbf25cc8d
--- /dev/null
+++ b/src/neighbor/Glue.cxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Glue.hxx"
+#include "Registry.hxx"
+#include "Explorer.hxx"
+#include "NeighborPlugin.hxx"
+#include "Info.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+
+NeighborGlue::Explorer::~Explorer()
+{
+ delete explorer;
+}
+
+NeighborGlue::~NeighborGlue() {}
+
+static NeighborExplorer *
+CreateNeighborExplorer(EventLoop &loop, NeighborListener &listener,
+ const config_param &param, Error &error)
+{
+ const char *plugin_name = param.GetBlockValue("plugin");
+ if (plugin_name == nullptr) {
+ error.Set(config_domain,
+ "Missing \"plugin\" configuration");
+ return nullptr;
+ }
+
+ const NeighborPlugin *plugin = GetNeighborPluginByName(plugin_name);
+ if (plugin == nullptr) {
+ error.Format(config_domain, "No such neighbor plugin: %s",
+ plugin_name);
+ return nullptr;
+ }
+
+ return plugin->create(loop, listener, param, error);
+}
+
+bool
+NeighborGlue::Init(EventLoop &loop, NeighborListener &listener, Error &error)
+{
+ for (const config_param *param = config_get_param(CONF_NEIGHBORS);
+ param != nullptr; param = param->next) {
+ NeighborExplorer *explorer =
+ CreateNeighborExplorer(loop, listener, *param, error);
+ if (explorer == nullptr) {
+ error.FormatPrefix("Line %i: ", param->line);
+ return false;
+ }
+
+ explorers.emplace_front(explorer);
+ }
+
+ return true;
+}
+
+bool
+NeighborGlue::Open(Error &error)
+{
+ for (auto i = explorers.begin(), end = explorers.end();
+ i != end; ++i) {
+ if (!i->explorer->Open(error)) {
+ /* roll back */
+ for (auto k = explorers.begin(); k != i; ++k)
+ k->explorer->Close();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+NeighborGlue::Close()
+{
+ for (auto i = explorers.begin(), end = explorers.end(); i != end; ++i)
+ i->explorer->Close();
+}
+
+NeighborGlue::List
+NeighborGlue::GetList() const
+{
+ List result;
+
+ for (const auto &i : explorers)
+ result.splice_after(result.before_begin(),
+ i.explorer->GetList());
+
+ return result;
+}
+
diff --git a/src/neighbor/Glue.hxx b/src/neighbor/Glue.hxx
new file mode 100644
index 000000000..92c612d22
--- /dev/null
+++ b/src/neighbor/Glue.hxx
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_ALL_HXX
+#define MPD_NEIGHBOR_ALL_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "thread/Mutex.hxx"
+
+#include <forward_list>
+
+struct config_param;
+class Error;
+class EventLoop;
+class NeighborExplorer;
+class NeighborListener;
+struct NeighborInfo;
+
+/**
+ * A class that initializes and opens all configured neighbor plugins.
+ */
+class NeighborGlue {
+ struct Explorer {
+ NeighborExplorer *const explorer;
+
+ Explorer(NeighborExplorer *_explorer):explorer(_explorer) {}
+ Explorer(const Explorer &) = delete;
+ ~Explorer();
+ };
+
+ Mutex mutex;
+
+ std::forward_list<Explorer> explorers;
+
+public:
+ typedef std::forward_list<NeighborInfo> List;
+
+ NeighborGlue() = default;
+ NeighborGlue(const NeighborGlue &) = delete;
+ ~NeighborGlue();
+
+ bool IsEmpty() const {
+ return explorers.empty();
+ }
+
+ bool Init(EventLoop &loop, NeighborListener &listener, Error &error);
+
+ bool Open(Error &error);
+ void Close();
+
+ /**
+ * Get the combined list of all neighbors from all active
+ * plugins.
+ */
+ gcc_pure
+ List GetList() const;
+};
+
+#endif
diff --git a/src/neighbor/Info.hxx b/src/neighbor/Info.hxx
new file mode 100644
index 000000000..ac4806f14
--- /dev/null
+++ b/src/neighbor/Info.hxx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_INFO_HXX
+#define MPD_NEIGHBOR_INFO_HXX
+
+#include <string>
+
+struct NeighborInfo {
+ std::string uri;
+ std::string display_name;
+
+ template<typename U, typename DN>
+ NeighborInfo(U &&_uri, DN &&_display_name)
+ :uri(std::forward<U>(_uri)),
+ display_name(std::forward<DN>(_display_name)) {}
+};
+
+#endif
diff --git a/src/neighbor/Listener.hxx b/src/neighbor/Listener.hxx
new file mode 100644
index 000000000..20295f5a9
--- /dev/null
+++ b/src/neighbor/Listener.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_LISTENER_HXX
+#define MPD_NEIGHBOR_LISTENER_HXX
+
+struct NeighborInfo;
+class NeighborExplorer;
+
+/**
+ * An interface that listens on events from neighbor plugins. The
+ * methods must be thread-safe and non-blocking.
+ */
+class NeighborListener {
+public:
+ virtual void FoundNeighbor(const NeighborInfo &info) = 0;
+ virtual void LostNeighbor(const NeighborInfo &info) = 0;
+};
+
+#endif
diff --git a/src/neighbor/NeighborPlugin.hxx b/src/neighbor/NeighborPlugin.hxx
new file mode 100644
index 000000000..0d4ebaa7b
--- /dev/null
+++ b/src/neighbor/NeighborPlugin.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_PLUGIN_HXX
+#define MPD_NEIGHBOR_PLUGIN_HXX
+
+struct config_param;
+class Error;
+class EventLoop;
+class NeighborListener;
+class NeighborExplorer;
+
+struct NeighborPlugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a #NeighborExplorer instance.
+ */
+ NeighborExplorer *(*create)(EventLoop &loop, NeighborListener &listener,
+ const config_param &param,
+ Error &error);
+};
+
+#endif
diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx
new file mode 100644
index 000000000..f6d1f97b3
--- /dev/null
+++ b/src/neighbor/Registry.cxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Registry.hxx"
+#include "NeighborPlugin.hxx"
+#include "plugins/SmbclientNeighborPlugin.hxx"
+#include "plugins/UpnpNeighborPlugin.hxx"
+
+#include <string.h>
+
+const NeighborPlugin *const neighbor_plugins[] = {
+#ifdef ENABLE_SMBCLIENT
+ &smbclient_neighbor_plugin,
+#endif
+#ifdef HAVE_LIBUPNP
+ &upnp_neighbor_plugin,
+#endif
+ nullptr
+};
+
+const NeighborPlugin *
+GetNeighborPluginByName(const char *name)
+{
+ for (auto i = neighbor_plugins; *i != nullptr; ++i)
+ if (strcmp((*i)->name, name) == 0)
+ return *i;
+
+ return nullptr;
+}
diff --git a/src/neighbor/Registry.hxx b/src/neighbor/Registry.hxx
new file mode 100644
index 000000000..0b89e537d
--- /dev/null
+++ b/src/neighbor/Registry.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_REGISTRY_HXX
+#define MPD_NEIGHBOR_REGISTRY_HXX
+
+#include "Compiler.h"
+
+struct NeighborPlugin;
+
+/**
+ * nullptr terminated list of all neighbor plugins which were enabled at
+ * compile time.
+ */
+extern const NeighborPlugin *const neighbor_plugins[];
+
+gcc_pure
+const NeighborPlugin *
+GetNeighborPluginByName(const char *name);
+
+#endif
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
new file mode 100644
index 000000000..2701b0ccd
--- /dev/null
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SmbclientNeighborPlugin.hxx"
+#include "lib/smbclient/Init.hxx"
+#include "lib/smbclient/Domain.hxx"
+#include "lib/smbclient/Mutex.hxx"
+#include "neighbor/NeighborPlugin.hxx"
+#include "neighbor/Explorer.hxx"
+#include "neighbor/Listener.hxx"
+#include "neighbor/Info.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "thread/Name.hxx"
+#include "util/Macros.hxx"
+#include "util/Domain.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <libsmbclient.h>
+
+#include <list>
+#include <algorithm>
+
+class SmbclientNeighborExplorer final : public NeighborExplorer {
+ struct Server {
+ std::string name, comment;
+
+ bool alive;
+
+ Server(std::string &&_name, std::string &&_comment)
+ :name(std::move(_name)), comment(std::move(_comment)),
+ alive(true) {}
+ Server(const Server &) = delete;
+
+ gcc_pure
+ bool operator==(const Server &other) const {
+ return name == other.name;
+ }
+
+ gcc_pure
+ NeighborInfo Export() const {
+ return { "smb://" + name + "/", comment };
+ }
+ };
+
+ Thread thread;
+
+ mutable Mutex mutex;
+ Cond cond;
+
+ List list;
+
+ bool quit;
+
+public:
+ SmbclientNeighborExplorer(NeighborListener &_listener)
+ :NeighborExplorer(_listener) {}
+
+ /* virtual methods from class NeighborExplorer */
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual List GetList() const override;
+
+private:
+ void Run();
+ void ThreadFunc();
+ static void ThreadFunc(void *ctx);
+};
+
+bool
+SmbclientNeighborExplorer::Open(Error &error)
+{
+ quit = false;
+ return thread.Start(ThreadFunc, this, error);
+}
+
+void
+SmbclientNeighborExplorer::Close()
+{
+ mutex.lock();
+ quit = true;
+ cond.signal();
+ mutex.unlock();
+
+ thread.Join();
+}
+
+NeighborExplorer::List
+SmbclientNeighborExplorer::GetList() const
+{
+ const ScopeLock protect(mutex);
+ /*
+ List list;
+ for (const auto &i : servers)
+ list.emplace_front(i.Export());
+ */
+ return list;
+}
+
+static void
+ReadServer(NeighborExplorer::List &list, const smbc_dirent &e)
+{
+ const std::string name(e.name, e.namelen);
+ const std::string comment(e.comment, e.commentlen);
+
+ list.emplace_front("smb://" + name, name + " (" + comment + ")");
+}
+
+static void
+ReadServers(NeighborExplorer::List &list, const char *uri);
+
+static void
+ReadWorkgroup(NeighborExplorer::List &list, const std::string &name)
+{
+ std::string uri = "smb://" + name;
+ ReadServers(list, uri.c_str());
+}
+
+static void
+ReadEntry(NeighborExplorer::List &list, const smbc_dirent &e)
+{
+ switch (e.smbc_type) {
+ case SMBC_WORKGROUP:
+ ReadWorkgroup(list, std::string(e.name, e.namelen));
+ break;
+
+ case SMBC_SERVER:
+ ReadServer(list, e);
+ break;
+ }
+}
+
+static void
+ReadServers(NeighborExplorer::List &list, int fd)
+{
+ smbc_dirent *e;
+ while ((e = smbc_readdir(fd)) != nullptr)
+ ReadEntry(list, *e);
+
+ smbc_closedir(fd);
+}
+
+static void
+ReadServers(NeighborExplorer::List &list, const char *uri)
+{
+ int fd = smbc_opendir(uri);
+ if (fd >= 0) {
+ ReadServers(list, fd);
+ smbc_closedir(fd);
+ } else
+ FormatErrno(smbclient_domain, "smbc_opendir('%s') failed",
+ uri);
+}
+
+gcc_pure
+static NeighborExplorer::List
+DetectServers()
+{
+ NeighborExplorer::List list;
+ const ScopeLock protect(smbclient_mutex);
+ ReadServers(list, "smb://");
+ return list;
+}
+
+gcc_pure
+static NeighborExplorer::List::const_iterator
+FindBeforeServerByURI(NeighborExplorer::List::const_iterator prev,
+ NeighborExplorer::List::const_iterator end,
+ const std::string &uri)
+{
+ for (auto i = std::next(prev); i != end; prev = i, i = std::next(prev))
+ if (i->uri == uri)
+ return prev;
+
+ return end;
+}
+
+inline void
+SmbclientNeighborExplorer::Run()
+{
+ List found = DetectServers(), lost;
+
+ mutex.lock();
+
+ const auto found_before_begin = found.before_begin();
+ const auto found_end = found.end();
+
+ for (auto prev = list.before_begin(), i = std::next(prev), end = list.end();
+ i != end; i = std::next(prev)) {
+ auto f = FindBeforeServerByURI(found_before_begin, found_end,
+ i->uri);
+ if (f != found_end) {
+ /* still visible: remove from "found" so we
+ don't believe it's a new one */
+ *i = std::move(*std::next(f));
+ found.erase_after(f);
+ prev = i;
+ } else {
+ /* can't see it anymore: move to "lost" */
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+ lost.splice_after(lost.before_begin(), list, prev);
+#else
+ /* the forward_list::splice_after() lvalue
+ reference overload is missing in gcc 4.6 */
+ lost.emplace_front(std::move(*i));
+ list.erase_after(prev);
+#endif
+ }
+ }
+
+ for (auto prev = found_before_begin, i = std::next(prev);
+ i != found_end; prev = i, i = std::next(prev))
+ list.push_front(*i);
+
+ mutex.unlock();
+
+ for (auto &i : lost)
+ listener.LostNeighbor(i);
+
+ for (auto &i : found)
+ listener.FoundNeighbor(i);
+}
+
+inline void
+SmbclientNeighborExplorer::ThreadFunc()
+{
+ mutex.lock();
+
+ while (!quit) {
+ mutex.unlock();
+
+ Run();
+
+ mutex.lock();
+ if (quit)
+ break;
+
+ // TODO: sleep for how long?
+ cond.timed_wait(mutex, 10000);
+ }
+
+ mutex.unlock();
+}
+
+void
+SmbclientNeighborExplorer::ThreadFunc(void *ctx)
+{
+ SetThreadName("smbclient");
+
+ SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
+ e.ThreadFunc();
+}
+
+static NeighborExplorer *
+smbclient_neighbor_create(gcc_unused EventLoop &loop,
+ NeighborListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ if (!SmbclientInit(error))
+ return nullptr;
+
+ return new SmbclientNeighborExplorer(listener);
+}
+
+const NeighborPlugin smbclient_neighbor_plugin = {
+ "smbclient",
+ smbclient_neighbor_create,
+};
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.hxx b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx
new file mode 100644
index 000000000..12ec9c0cd
--- /dev/null
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_SMBCLIENT_HXX
+#define MPD_NEIGHBOR_SMBCLIENT_HXX
+
+struct NeighborPlugin;
+
+extern const NeighborPlugin smbclient_neighbor_plugin;
+
+#endif
diff --git a/src/neighbor/plugins/UpnpNeighborPlugin.cxx b/src/neighbor/plugins/UpnpNeighborPlugin.cxx
new file mode 100644
index 000000000..253e4c13b
--- /dev/null
+++ b/src/neighbor/plugins/UpnpNeighborPlugin.cxx
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "UpnpNeighborPlugin.hxx"
+#include "lib/upnp/Domain.hxx"
+#include "lib/upnp/ClientInit.hxx"
+#include "lib/upnp/Discovery.hxx"
+#include "lib/upnp/ContentDirectoryService.hxx"
+#include "neighbor/NeighborPlugin.hxx"
+#include "neighbor/Explorer.hxx"
+#include "neighbor/Listener.hxx"
+#include "neighbor/Info.hxx"
+#include "Log.hxx"
+
+class UpnpNeighborExplorer final
+ : public NeighborExplorer, UPnPDiscoveryListener {
+ struct Server {
+ std::string name, comment;
+
+ bool alive;
+
+ Server(std::string &&_name, std::string &&_comment)
+ :name(std::move(_name)), comment(std::move(_comment)),
+ alive(true) {}
+ Server(const Server &) = delete;
+
+ gcc_pure
+ bool operator==(const Server &other) const {
+ return name == other.name;
+ }
+
+ gcc_pure
+ NeighborInfo Export() const {
+ return { "smb://" + name + "/", comment };
+ }
+ };
+
+ UPnPDeviceDirectory *discovery;
+
+public:
+ UpnpNeighborExplorer(NeighborListener &_listener)
+ :NeighborExplorer(_listener) {}
+
+ /* virtual methods from class NeighborExplorer */
+ virtual bool Open(Error &error) override;
+ virtual void Close() override;
+ virtual List GetList() const override;
+
+private:
+ /* virtual methods from class UPnPDiscoveryListener */
+ virtual void FoundUPnP(const ContentDirectoryService &service) override;
+ virtual void LostUPnP(const ContentDirectoryService &service) override;
+};
+
+bool
+UpnpNeighborExplorer::Open(Error &error)
+{
+ UpnpClient_Handle handle;
+ if (!UpnpClientGlobalInit(handle, error))
+ return false;
+
+ discovery = new UPnPDeviceDirectory(handle, this);
+ if (!discovery->Start(error)) {
+ delete discovery;
+ UpnpClientGlobalFinish();
+ return false;
+ }
+
+ return true;
+}
+
+void
+UpnpNeighborExplorer::Close()
+{
+ delete discovery;
+ UpnpClientGlobalFinish();
+}
+
+NeighborExplorer::List
+UpnpNeighborExplorer::GetList() const
+{
+ std::vector<ContentDirectoryService> tmp;
+
+ {
+ Error error;
+ if (!discovery->getDirServices(tmp, error))
+ LogError(error);
+ }
+
+ List result;
+ for (const auto &i : tmp)
+ result.emplace_front(i.GetURI(), i.getFriendlyName());
+ return result;
+}
+
+void
+UpnpNeighborExplorer::FoundUPnP(const ContentDirectoryService &service)
+{
+ const NeighborInfo n(service.GetURI(), service.getFriendlyName());
+ listener.FoundNeighbor(n);
+}
+
+void
+UpnpNeighborExplorer::LostUPnP(const ContentDirectoryService &service)
+{
+ const NeighborInfo n(service.GetURI(), service.getFriendlyName());
+ listener.LostNeighbor(n);
+}
+
+static NeighborExplorer *
+upnp_neighbor_create(gcc_unused EventLoop &loop,
+ NeighborListener &listener,
+ gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new UpnpNeighborExplorer(listener);
+}
+
+const NeighborPlugin upnp_neighbor_plugin = {
+ "upnp",
+ upnp_neighbor_create,
+};
diff --git a/src/neighbor/plugins/UpnpNeighborPlugin.hxx b/src/neighbor/plugins/UpnpNeighborPlugin.hxx
new file mode 100644
index 000000000..78e4ccf13
--- /dev/null
+++ b/src/neighbor/plugins/UpnpNeighborPlugin.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_UPNP_HXX
+#define MPD_NEIGHBOR_UPNP_HXX
+
+struct NeighborPlugin;
+
+extern const NeighborPlugin upnp_neighbor_plugin;
+
+#endif
diff --git a/src/notify.cxx b/src/notify.cxx
index 64018968c..5b0b4baa0 100644
--- a/src/notify.cxx
+++ b/src/notify.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/notify.hxx b/src/notify.hxx
index 1024dd8d9..3e62a0103 100644
--- a/src/notify.hxx
+++ b/src/notify.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,7 +28,7 @@ struct notify {
Cond cond;
bool pending;
-#if !defined(WIN32) && !defined(__NetBSD__)
+#if !defined(WIN32) && !defined(__NetBSD__) && !defined(__BIONIC__)
constexpr
#endif
notify():pending(false) {}
diff --git a/src/open.h b/src/open.h
index 1fe245dfa..b05167188 100644
--- a/src/open.h
+++ b/src/open.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
deleted file mode 100644
index f8aae13a1..000000000
--- a/src/output/AlsaOutputPlugin.cxx
+++ /dev/null
@@ -1,869 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AlsaOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "pcm/PcmExport.hxx"
-#include "util/Manual.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <alsa/asoundlib.h>
-
-#include <string>
-
-#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;
-
- /**
- * Do we need to call snd_pcm_prepare() before the next write?
- * It means that we put the device to SND_PCM_STATE_SETUP by
- * calling snd_pcm_drop().
- *
- * Without this flag, we could easily recover after a failed
- * optimistic write (returning -EBADFD), but the Raspberry Pi
- * audio driver is infamous for generating ugly artefacts from
- * this.
- */
- bool must_prepare;
-
- /**
- * 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, Error &error) {
- return ao_base_init(&base, &alsa_output_plugin,
- param, error);
- }
-
- void Deinit() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain alsa_output_domain("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, Error &error)
-{
- AlsaOutput *ad = new AlsaOutput();
-
- if (!ad->Init(param, error)) {
- delete ad;
- return nullptr;
- }
-
- 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, gcc_unused Error &error)
-{
- 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) {
- FormatError(alsa_output_domain,
- "Error opening default ALSA device: %s",
- 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, Error &error)
-{
- unsigned int sample_rate = audio_format.sample_rate;
- unsigned int channels = audio_format.channels;
- int err;
- const char *cmd = nullptr;
- 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) {
- FormatWarning(alsa_output_domain,
- "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
- alsa_device(ad), snd_strerror(-err));
- LogWarning(alsa_output_domain,
- "Falling back to direct write mode");
- 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) {
- error.Format(alsa_output_domain, 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)
- FormatDebug(alsa_output_domain,
- "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) {
- error.Format(alsa_output_domain, 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, nullptr);
- if (err < 0 || sample_rate == 0) {
- error.Format(alsa_output_domain, 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);
- FormatDebug(alsa_output_domain, "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);
- FormatDebug(alsa_output_domain, "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, nullptr);
- if (err < 0)
- goto error;
- } else {
- err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
- nullptr);
- if (err < 0)
- buffer_time = 0;
- }
-
- if (period_time_ro == 0 && buffer_time >= 10000) {
- period_time_ro = period_time = buffer_time / 4;
-
- FormatDebug(alsa_output_domain,
- "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, nullptr);
- 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)
- FormatDebug(alsa_output_domain,
- "ALSA period_time set to %d", 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,
- nullptr);
- 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;
-
- FormatDebug(alsa_output_domain, "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:
- error.Format(alsa_output_domain, 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,
- Error &error)
-{
- 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))
- 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 */
- error.Format(alsa_output_domain,
- "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,
- Error &error)
-{
- 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)
- : alsa_setup(ad, audio_format, &packed, &reverse_endian,
- error);
- 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, Error &error)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
- SND_PCM_STREAM_PLAYBACK, ad->mode);
- if (err < 0) {
- error.Format(alsa_output_domain, err,
- "Failed to open ALSA device \"%s\": %s",
- alsa_device(ad), snd_strerror(err));
- return false;
- }
-
- FormatDebug(alsa_output_domain, "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);
-
- ad->must_prepare = false;
-
- 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) {
- FormatDebug(alsa_output_domain,
- "Underrun on ALSA device \"%s\"", alsa_device(ad));
- } else if (err == -ESTRPIPE) {
- FormatDebug(alsa_output_domain,
- "ALSA device \"%s\" was suspended",
- 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)
-{
- 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;
- ad->must_prepare = true;
-
- 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,
- Error &error)
-{
- AlsaOutput *ad = (AlsaOutput *)ao;
-
- assert(size > 0);
- assert(size % ad->in_frame_size == 0);
-
- if (ad->must_prepare) {
- ad->must_prepare = false;
-
- int err = snd_pcm_prepare(ad->pcm);
- if (err < 0) {
- error.Set(alsa_output_domain, err, snd_strerror(-err));
- return 0;
- }
- }
-
- const size_t original_size = size;
- chunk = ad->pcm_export->Export(chunk, size, size);
- if (size == 0)
- /* the DoP (DSD over PCM) filter converts two frames
- at a time and ignores the last odd frame; if there
- was only one frame (e.g. the last frame in the
- file), the result is empty; to avoid an endless
- loop, bail out here, and pretend the one frame has
- been played */
- return original_size;
-
- assert(size % ad->out_frame_size == 0);
-
- size /= ad->out_frame_size;
- assert(size > 0);
-
- 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) {
- error.Set(alsa_output_domain, ret, snd_strerror(-ret));
- 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
deleted file mode 100644
index dc7e639a8..000000000
--- a/src/output/AlsaOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index e66969e20..000000000
--- a/src/output/AoOutputPlugin.cxx
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AoOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <ao/ao.h>
-#include <glib.h>
-
-#include <string.h>
-
-/* 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, Error &error) {
- return ao_base_init(&base, &ao_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain ao_output_domain("ao_output");
-
-static void
-ao_output_error(Error &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_r.SetErrno();
- return;
- }
-
- error_r.Set(ao_output_domain, errno, error);
-}
-
-inline bool
-AoOutput::Configure(const config_param &param, Error &error)
-{
- 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) {
- error.Format(ao_output_domain,
- "\"%s\" is not a valid ao driver",
- value);
- return false;
- }
-
- ao_info *ai = ao_driver_info(driver);
- if (ai == nullptr) {
- error.Set(ao_output_domain, "problems getting driver info");
- return false;
- }
-
- FormatDebug(ao_output_domain, "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) {
- error.Format(ao_output_domain,
- "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, Error &error)
-{
- AoOutput *ad = new AoOutput();
-
- if (!ad->Initialize(param, error)) {
- delete ad;
- return nullptr;
- }
-
- if (!ad->Configure(param, error)) {
- 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,
- Error &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,
- Error &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
deleted file mode 100644
index a44885e56..000000000
--- a/src/output/AoOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/Domain.cxx b/src/output/Domain.cxx
new file mode 100644
index 000000000..878e5f3c5
--- /dev/null
+++ b/src/output/Domain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain output_domain("output");
diff --git a/src/output/Domain.hxx b/src/output/Domain.hxx
new file mode 100644
index 000000000..e3a20142f
--- /dev/null
+++ b/src/output/Domain.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_ERROR_HXX
+#define MPD_OUTPUT_ERROR_HXX
+
+extern const class Domain output_domain;
+
+#endif
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
deleted file mode 100644
index aeb9a6a87..000000000
--- a/src/output/FifoOutputPlugin.cxx
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "FifoOutputPlugin.hxx"
-#include "ConfigError.hxx"
-#include "OutputAPI.hxx"
-#include "Timer.hxx"
-#include "system/fd_util.h"
-#include "fs/AllocatedPath.hxx"
-#include "fs/FileSystem.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-#include "open.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
-
-struct FifoOutput {
- struct audio_output base;
-
- AllocatedPath path;
- std::string path_utf8;
-
- int input;
- int output;
- bool created;
- Timer *timer;
-
- FifoOutput()
- :path(AllocatedPath::Null()), input(-1), output(-1),
- created(false) {}
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &fifo_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Create(Error &error);
- bool Check(Error &error);
- void Delete();
-
- bool Open(Error &error);
- void Close();
-};
-
-static constexpr Domain fifo_output_domain("fifo_output");
-
-inline void
-FifoOutput::Delete()
-{
- FormatDebug(fifo_output_domain,
- "Removing FIFO \"%s\"", path_utf8.c_str());
-
- if (!RemoveFile(path)) {
- FormatErrno(fifo_output_domain,
- "Could not remove FIFO \"%s\"",
- path_utf8.c_str());
- return;
- }
-
- created = false;
-}
-
-void
-FifoOutput::Close()
-{
- if (input >= 0) {
- close(input);
- input = -1;
- }
-
- if (output >= 0) {
- close(output);
- output = -1;
- }
-
- struct stat st;
- if (created && StatFile(path, st))
- Delete();
-}
-
-inline bool
-FifoOutput::Create(Error &error)
-{
- if (!MakeFifo(path, 0666)) {
- error.FormatErrno("Couldn't create FIFO \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- created = true;
- return true;
-}
-
-inline bool
-FifoOutput::Check(Error &error)
-{
- struct stat st;
- if (!StatFile(path, st)) {
- if (errno == ENOENT) {
- /* Path doesn't exist */
- return Create(error);
- }
-
- error.FormatErrno("Failed to stat FIFO \"%s\"",
- path_utf8.c_str());
- return false;
- }
-
- if (!S_ISFIFO(st.st_mode)) {
- error.Format(fifo_output_domain,
- "\"%s\" already exists, but is not a FIFO",
- path_utf8.c_str());
- return false;
- }
-
- return true;
-}
-
-inline bool
-FifoOutput::Open(Error &error)
-{
- if (!Check(error))
- return false;
-
- input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
- if (input < 0) {
- error.FormatErrno("Could not open FIFO \"%s\" for reading",
- path_utf8.c_str());
- Close();
- return false;
- }
-
- output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
- if (output < 0) {
- error.FormatErrno("Could not open FIFO \"%s\" for writing",
- path_utf8.c_str());
- Close();
- return false;
- }
-
- return true;
-}
-
-static bool
-fifo_open(FifoOutput *fd, Error &error)
-{
- return fd->Open(error);
-}
-
-static struct audio_output *
-fifo_output_init(const config_param &param, Error &error)
-{
- FifoOutput *fd = new FifoOutput();
-
- fd->path = param.GetBlockPath("path", error);
- if (fd->path.IsNull()) {
- delete fd;
-
- if (!error.IsDefined())
- error.Set(config_domain,
- "No \"path\" parameter specified");
- return nullptr;
- }
-
- fd->path_utf8 = fd->path.ToUTF8();
-
- if (!fd->Initialize(param, error)) {
- delete fd;
- return nullptr;
- }
-
- if (!fifo_open(fd, error)) {
- 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,
- gcc_unused Error &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) {
- FormatErrno(fifo_output_domain,
- "Flush of FIFO \"%s\" failed",
- fd->path_utf8.c_str());
- }
-}
-
-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,
- Error &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;
- }
-
- error.FormatErrno("Failed to write to FIFO %s",
- fd->path_utf8.c_str());
- 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
deleted file mode 100644
index dca2886d8..000000000
--- a/src/output/FifoOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/Finish.cxx b/src/output/Finish.cxx
new file mode 100644
index 000000000..be2ca463e
--- /dev/null
+++ b/src/output/Finish.cxx
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Internal.hxx"
+#include "OutputPlugin.hxx"
+#include "mixer/MixerControl.hxx"
+#include "filter/FilterInternal.hxx"
+
+#include <assert.h>
+
+AudioOutput::~AudioOutput()
+{
+ assert(!open);
+ assert(!fail_timer.IsDefined());
+ assert(!thread.IsDefined());
+
+ if (mixer != nullptr)
+ mixer_free(mixer);
+
+ delete replay_gain_filter;
+ delete other_replay_gain_filter;
+ delete filter;
+}
+
+void
+audio_output_free(AudioOutput *ao)
+{
+ assert(!ao->open);
+ assert(!ao->fail_timer.IsDefined());
+ assert(!ao->thread.IsDefined());
+
+ ao_plugin_finish(ao);
+}
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
deleted file mode 100644
index dc337053d..000000000
--- a/src/output/HttpdClient.cxx
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "HttpdClient.hxx"
-#include "HttpdInternal.hxx"
-#include "util/ASCII.hxx"
-#include "Page.hxx"
-#include "IcyMetaDataServer.hxx"
-#include "system/SocketError.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-#include <stdio.h>
-
-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;
-
- if (!head_method)
- httpd->SendHeader(*this);
-}
-
-/**
- * Handle a line of the HTTP request.
- */
-bool
-HttpdClient::HandleLine(const char *line)
-{
- assert(state != RESPONSE);
-
- if (state == REQUEST) {
- if (memcmp(line, "HEAD /", 6) == 0) {
- line += 6;
- head_method = true;
- } else if (memcmp(line, "GET /", 5) == 0) {
- line += 5;
- } else {
- /* only GET is supported */
- LogWarning(httpd_output_domain,
- "malformed request line from client");
- return false;
- }
-
- line = strchr(line, ' ');
- if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
- /* HTTP/0.9 without request headers */
-
- if (head_method)
- return false;
-
- 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 (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
- StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
- /* Send icy metadata */
- metadata_requested = metadata_supported;
- return true;
- }
-
- if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
- /* Send as dlna */
- dlna_streaming_requested = true;
- /* metadata is not supported by dlna streaming, so disable it */
- metadata_supported = false;
- metadata_requested = false;
- return true;
- }
-
- /* expect more request headers */
- 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) {
- 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) {
- char *metadata_header =
- icy_server_metadata_header(httpd->name, httpd->genre,
- httpd->website,
- httpd->content_type,
- metaint);
-
- g_strlcpy(buffer, metadata_header, sizeof(buffer));
-
- delete[] metadata_header;
-
- } else { /* revert to a normal HTTP request */
- 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;
- FormatWarning(httpd_output_domain,
- "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),
- head_method(false),
- 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);
- FormatWarning(httpd_output_domain,
- "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);
- FormatWarning(httpd_output_domain,
- "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);
- FormatWarning(httpd_output_domain,
- "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(void *data, size_t length)
-{
- if (state == RESPONSE) {
- LogWarning(httpd_output_domain,
- "unexpected input from client");
- LockClose();
- return InputResult::CLOSED;
- }
-
- char *line = (char *)data;
- char *newline = (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 */
- *newline = 0;
-
- if (!HandleLine(line)) {
- LockClose();
- return InputResult::CLOSED;
- }
-
- if (state == RESPONSE) {
- if (!SendResponse())
- return InputResult::CLOSED;
-
- if (head_method) {
- LockClose();
- return InputResult::CLOSED;
- }
- }
-
- return InputResult::AGAIN;
-}
-
-void
-HttpdClient::OnSocketError(Error &&error)
-{
- LogError(error);
-}
-
-void
-HttpdClient::OnSocketClosed()
-{
- LockClose();
-}
diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx
deleted file mode 100644
index 66a819232..000000000
--- a/src/output/HttpdClient.hxx
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX
-#define MPD_OUTPUT_HTTPD_CLIENT_HXX
-
-#include "event/BufferedSocket.hxx"
-#include "Compiler.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;
-
- /**
- * Is this a HEAD request?
- */
- bool head_method;
-
- /**
- * 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
- */
- unsigned 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.
- */
- unsigned 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(void *data, size_t length) override;
- virtual void OnSocketError(Error &&error) override;
- virtual void OnSocketClosed() override;
-};
-
-#endif
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
deleted file mode 100644
index b76493a44..000000000
--- a/src/output/HttpdInternal.hxx
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Internal declarations for the "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"
-
-#ifdef _LIBCPP_VERSION
-/* can't use incomplete template arguments with libc++ */
-#include "HttpdClient.hxx"
-#endif
-
-#include <forward_list>
-
-struct config_param;
-class Error;
-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.
- */
- unsigned clients_max, clients_cnt;
-
- HttpdOutput(EventLoop &_loop);
- ~HttpdOutput();
-
- bool Configure(const config_param &param, Error &error);
-
- bool Bind(Error &error);
- void Unbind();
-
- /**
- * Caller must lock the mutex.
- */
- bool OpenEncoder(AudioFormat &audio_format, Error &error);
-
- /**
- * Caller must lock the mutex.
- */
- bool Open(AudioFormat &audio_format, Error &error);
-
- /**
- * 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, Error &error);
-
- void SendTag(const Tag *tag);
-
-private:
- virtual void OnAccept(int fd, const sockaddr &address,
- size_t address_length, int uid) override;
-};
-
-extern const class Domain httpd_output_domain;
-
-#endif
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
deleted file mode 100644
index 369c06937..000000000
--- a/src/output/HttpdOutputPlugin.cxx
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "HttpdOutputPlugin.hxx"
-#include "HttpdInternal.hxx"
-#include "HttpdClient.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "system/Resolver.hxx"
-#include "Page.hxx"
-#include "IcyMetaDataServer.hxx"
-#include "system/fd_util.h"
-#include "Main.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#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
-
-const Domain httpd_output_domain("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(Error &error)
-{
- open = false;
-
- const ScopeLock protect(mutex);
- return ServerSocket::Open(error);
-}
-
-inline void
-HttpdOutput::Unbind()
-{
- assert(!open);
-
- const ScopeLock protect(mutex);
- ServerSocket::Close();
-}
-
-inline bool
-HttpdOutput::Configure(const config_param &param, Error &error)
-{
- /* 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");
-
- unsigned 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 == nullptr) {
- error.Format(httpd_output_domain,
- "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 != nullptr &&
- strcmp(bind_to_address, "any") != 0
- ? AddHost(bind_to_address, port, error)
- : AddPort(port, error);
- if (!success)
- return false;
-
- /* initialize encoder */
-
- encoder = encoder_init(*encoder_plugin, param, error);
- 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 config_param &param, Error &error)
-{
- HttpdOutput *httpd = new HttpdOutput(*main_loop);
-
- if (!ao_base_init(&httpd->base, &httpd_output_plugin, param,
- error)) {
- delete httpd;
- return nullptr;
- }
-
- if (!httpd->Configure(param, error)) {
- ao_base_finish(&httpd->base);
- delete httpd;
- return nullptr;
- }
-
- return &httpd->base;
-}
-
-#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Winvalid-offsetof"
-#endif
-
-static inline constexpr HttpdOutput *
-Cast(audio_output *ao)
-{
- return (HttpdOutput *)((char *)ao - offsetof(HttpdOutput, base));
-}
-
-#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,
- IgnoreError());
- 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 */
- FormatWarning(httpd_output_domain,
- "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) {
- LogErrno(httpd_output_domain, "accept() failed");
- }
-}
-
-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, IgnoreError());
- 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 nullptr;
-
- return Page::Copy(buffer, size);
-}
-
-static bool
-httpd_output_enable(struct audio_output *ao, Error &error)
-{
- HttpdOutput *httpd = Cast(ao);
-
- return httpd->Bind(error);
-}
-
-static void
-httpd_output_disable(struct audio_output *ao)
-{
- HttpdOutput *httpd = Cast(ao);
-
- httpd->Unbind();
-}
-
-inline bool
-HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &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, Error &error)
-{
- assert(!open);
- assert(clients.empty());
-
- /* open the encoder */
-
- if (!OpenEncoder(audio_format, error))
- 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,
- Error &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 != nullptr)
- 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 != nullptr)
- 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 != nullptr);
-
- 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) {
- FormatDebug(httpd_output_domain,
- "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, Error &error)
-{
- if (!encoder_write(encoder, chunk, size, error))
- return false;
-
- unflushed_input += size;
-
- BroadcastFromEncoder();
- return true;
-}
-
-static size_t
-httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- HttpdOutput *httpd = Cast(ao);
-
- if (httpd->LockHasClients()) {
- if (!httpd->EncodeAndPlay(chunk, size, error))
- 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),
- IgnoreError()) > 0;
- } else {
- return true;
- }
-}
-
-inline void
-HttpdOutput::SendTag(const Tag *tag)
-{
- assert(tag != nullptr);
-
- if (encoder->plugin.tag != nullptr) {
- /* embed encoder tags */
-
- /* flush the current stream, and end it */
-
- encoder_pre_tag(encoder, IgnoreError());
- BroadcastFromEncoder();
-
- /* send the tag to the encoder - which starts a new
- stream now */
-
- encoder_tag(encoder, tag, IgnoreError());
-
- /* 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 != nullptr) {
- if (header != nullptr)
- header->Unref();
- header = page;
- BroadcastPage(page);
- }
- } else {
- /* use Icy-Metadata */
-
- if (metadata != nullptr)
- metadata->Unref();
-
- static constexpr TagType types[] = {
- TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
- TAG_NUM_OF_ITEM_TYPES
- };
-
- metadata = icy_server_metadata_page(*tag, &types[0]);
- if (metadata != nullptr) {
- 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
deleted file mode 100644
index c74d2bd4a..000000000
--- a/src/output/HttpdOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/Init.cxx b/src/output/Init.cxx
new file mode 100644
index 000000000..79ef4f998
--- /dev/null
+++ b/src/output/Init.cxx
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Internal.hxx"
+#include "Registry.hxx"
+#include "Domain.hxx"
+#include "OutputAPI.hxx"
+#include "filter/FilterConfig.hxx"
+#include "AudioParser.hxx"
+#include "mixer/MixerList.hxx"
+#include "mixer/MixerType.hxx"
+#include "mixer/MixerControl.hxx"
+#include "mixer/plugins/SoftwareMixerPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "filter/plugins/AutoConvertFilterPlugin.hxx"
+#include "filter/plugins/ReplayGainFilterPlugin.hxx"
+#include "filter/plugins/ChainFilterPlugin.hxx"
+#include "config/ConfigError.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+#define AUDIO_OUTPUT_TYPE "type"
+#define AUDIO_OUTPUT_NAME "name"
+#define AUDIO_OUTPUT_FORMAT "format"
+#define AUDIO_FILTERS "filters"
+
+AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin)
+ :plugin(_plugin),
+ mixer(nullptr),
+ enabled(true), really_enabled(false),
+ open(false),
+ pause(false),
+ allow_play(true),
+ in_playback_loop(false),
+ woken_for_play(false),
+ filter(nullptr),
+ replay_gain_filter(nullptr),
+ other_replay_gain_filter(nullptr),
+ command(AO_COMMAND_NONE)
+{
+ assert(plugin.finish != nullptr);
+ assert(plugin.open != nullptr);
+ assert(plugin.close != nullptr);
+ assert(plugin.play != nullptr);
+}
+
+static const AudioOutputPlugin *
+audio_output_detect(Error &error)
+{
+ LogDefault(output_domain, "Attempt to detect audio output device");
+
+ audio_output_plugins_for_each(plugin) {
+ if (plugin->test_default_device == nullptr)
+ continue;
+
+ FormatDefault(output_domain,
+ "Attempting to detect a %s audio device",
+ plugin->name);
+ if (ao_plugin_test_default_device(plugin))
+ return plugin;
+ }
+
+ error.Set(output_domain, "Unable to detect an audio device");
+ return nullptr;
+}
+
+/**
+ * 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 != nullptr)
+ 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(EventLoop &event_loop, AudioOutput &ao,
+ const config_param &param,
+ const MixerPlugin *plugin,
+ Filter &filter_chain,
+ MixerListener &listener,
+ Error &error)
+{
+ Mixer *mixer;
+
+ switch (audio_output_mixer_type(param)) {
+ case MIXER_TYPE_NONE:
+ case MIXER_TYPE_UNKNOWN:
+ return nullptr;
+
+ case MIXER_TYPE_HARDWARE:
+ if (plugin == nullptr)
+ return nullptr;
+
+ return mixer_new(event_loop, *plugin, ao, listener,
+ param, error);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(event_loop, software_mixer_plugin, ao,
+ listener,
+ config_param(),
+ IgnoreError());
+ assert(mixer != nullptr);
+
+ filter_chain_append(filter_chain, "software_mixer",
+ software_mixer_get_filter(mixer));
+ return mixer;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+bool
+AudioOutput::Configure(const config_param &param, Error &error)
+{
+ if (!param.IsNull()) {
+ name = param.GetBlockValue(AUDIO_OUTPUT_NAME);
+ if (name == nullptr) {
+ error.Set(config_domain,
+ "Missing \"name\" configuration");
+ return false;
+ }
+
+ const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT);
+ if (p != nullptr) {
+ bool success =
+ audio_format_parse(config_audio_format,
+ p, true, error);
+ if (!success)
+ return false;
+ } else
+ config_audio_format.Clear();
+ } else {
+ name = "default detected output";
+
+ config_audio_format.Clear();
+ }
+
+ tags = param.GetBlockValue("tags", true);
+ always_on = param.GetBlockValue("always_on", false);
+ enabled = param.GetBlockValue("enabled", true);
+
+ /* set up the filter chain */
+
+ filter = filter_chain_new();
+ assert(filter != nullptr);
+
+ /* create the normalization filter (if configured) */
+
+ if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) {
+ Filter *normalize_filter =
+ filter_new(&normalize_filter_plugin, config_param(),
+ IgnoreError());
+ assert(normalize_filter != nullptr);
+
+ filter_chain_append(*filter, "normalize",
+ autoconvert_filter_new(normalize_filter));
+ }
+
+ Error filter_error;
+ filter_chain_parse(*filter,
+ param.GetBlockValue(AUDIO_FILTERS, ""),
+ filter_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 (filter_error.IsDefined())
+ FormatError(filter_error,
+ "Failed to initialize filter chain for '%s'",
+ name);
+
+ /* done */
+
+ return true;
+}
+
+static bool
+audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
+ MixerListener &mixer_listener,
+ const config_param &param,
+ Error &error)
+{
+
+ /* 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, IgnoreError());
+ assert(ao.replay_gain_filter != nullptr);
+
+ ao.replay_gain_serial = 0;
+
+ ao.other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
+ param,
+ IgnoreError());
+ assert(ao.other_replay_gain_filter != nullptr);
+
+ ao.other_replay_gain_serial = 0;
+ } else {
+ ao.replay_gain_filter = nullptr;
+ ao.other_replay_gain_filter = nullptr;
+ }
+
+ /* set up the mixer */
+
+ Error mixer_error;
+ ao.mixer = audio_output_load_mixer(event_loop, ao, param,
+ ao.plugin.mixer_plugin,
+ *ao.filter,
+ mixer_listener,
+ mixer_error);
+ if (ao.mixer == nullptr && mixer_error.IsDefined())
+ FormatError(mixer_error,
+ "Failed to initialize hardware mixer for '%s'",
+ ao.name);
+
+ /* use the hardware mixer for replay gain? */
+
+ if (strcmp(replay_gain_handler, "mixer") == 0) {
+ if (ao.mixer != nullptr)
+ replay_gain_filter_set_mixer(ao.replay_gain_filter,
+ ao.mixer, 100);
+ else
+ FormatError(output_domain,
+ "No such mixer for output '%s'", ao.name);
+ } else if (strcmp(replay_gain_handler, "software") != 0 &&
+ ao.replay_gain_filter != nullptr) {
+ error.Set(config_domain,
+ "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(),
+ IgnoreError());
+ assert(ao.convert_filter != nullptr);
+
+ filter_chain_append(*ao.filter, "convert", ao.convert_filter);
+
+ return true;
+}
+
+AudioOutput *
+audio_output_new(EventLoop &event_loop, const config_param &param,
+ MixerListener &mixer_listener,
+ PlayerControl &pc,
+ Error &error)
+{
+ const AudioOutputPlugin *plugin;
+
+ if (!param.IsNull()) {
+ const char *p;
+
+ p = param.GetBlockValue(AUDIO_OUTPUT_TYPE);
+ if (p == nullptr) {
+ error.Set(config_domain,
+ "Missing \"type\" configuration");
+ return nullptr;
+ }
+
+ plugin = AudioOutputPlugin_get(p);
+ if (plugin == nullptr) {
+ error.Format(config_domain,
+ "No such audio output plugin: %s", p);
+ return nullptr;
+ }
+ } else {
+ LogWarning(output_domain,
+ "No 'AudioOutput' defined in config file");
+
+ plugin = audio_output_detect(error);
+ if (plugin == nullptr)
+ return nullptr;
+
+ FormatDefault(output_domain,
+ "Successfully detected a %s audio device",
+ plugin->name);
+ }
+
+ AudioOutput *ao = ao_plugin_init(plugin, param, error);
+ if (ao == nullptr)
+ return nullptr;
+
+ if (!audio_output_setup(event_loop, *ao, mixer_listener,
+ param, error)) {
+ ao_plugin_finish(ao);
+ return nullptr;
+ }
+
+ ao->player_control = &pc;
+ return ao;
+}
diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx
new file mode 100644
index 000000000..6e6ffb442
--- /dev/null
+++ b/src/output/Internal.hxx
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_INTERNAL_HXX
+#define MPD_OUTPUT_INTERNAL_HXX
+
+#include "AudioFormat.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "pcm/PcmDither.hxx"
+#include "ReplayGainInfo.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "system/PeriodClock.hxx"
+
+class Error;
+class Filter;
+class MusicPipe;
+class EventLoop;
+class Mixer;
+class MixerListener;
+struct MusicChunk;
+struct config_param;
+struct PlayerControl;
+struct AudioOutputPlugin;
+
+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 AudioOutput {
+ /**
+ * The device's configured display name.
+ */
+ const char *name;
+
+ /**
+ * The plugin which implements this output device.
+ */
+ const AudioOutputPlugin &plugin;
+
+ /**
+ * The #mixer object associated with this audio output device.
+ * May be nullptr if none is available, or if software volume is
+ * configured.
+ */
+ 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;
+
+ /**
+ * True while the OutputThread is inside ao_play(). This
+ * means the PlayerThread does not need to wake up the
+ * OutputThread when new chunks are added to the MusicPipe,
+ * because the OutputThread is already watching that.
+ */
+ bool in_playback_loop;
+
+ /**
+ * Has the OutputThread been woken up to play more chunks?
+ * This is set by audio_output_play() and reset by ao_play()
+ * to reduce the number of duplicate wakeups.
+ */
+ bool woken_for_play;
+
+ /**
+ * If not nullptr, the device has failed, and this timer is used
+ * to estimate how long it should stay disabled (unless
+ * explicitly reopened with "play").
+ */
+ PeriodClock 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 dithering state for cross-fading two streams.
+ */
+ PcmDither cross_fade_dither;
+
+ /**
+ * The filter object of this audio output. This is an
+ * instance of chain_filter_plugin.
+ */
+ 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 nullptr if the output thread isn't
+ * running.
+ */
+ Thread 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 MusicPipe *pipe;
+
+ /**
+ * This mutex protects #open, #fail_timer, #current_chunk and
+ * #current_chunk_finished.
+ */
+ Mutex mutex;
+
+ /**
+ * This condition object wakes up the output thread after
+ * #command has been set.
+ */
+ Cond cond;
+
+ /**
+ * The PlayerControl object which "owns" this output. This
+ * object is needed to signal command completion.
+ */
+ PlayerControl *player_control;
+
+ /**
+ * The #MusicChunk 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 MusicChunk *current_chunk;
+
+ /**
+ * Has the output finished playing #current_chunk?
+ */
+ bool current_chunk_finished;
+
+ AudioOutput(const AudioOutputPlugin &_plugin);
+ ~AudioOutput();
+
+ bool Configure(const config_param &param, Error &error);
+
+ void StartThread();
+ void StopThread();
+
+ void Finish();
+
+ bool IsOpen() const {
+ return open;
+ }
+
+ bool IsCommandFinished() const {
+ return command == AO_COMMAND_NONE;
+ }
+
+ /**
+ * Waits for command completion.
+ *
+ * Caller must lock the mutex.
+ */
+ void WaitForCommand();
+
+ /**
+ * Sends a command, but does not wait for completion.
+ *
+ * Caller must lock the mutex.
+ */
+ void CommandAsync(audio_output_command cmd);
+
+ /**
+ * Sends a command to the #AudioOutput object and waits for
+ * completion.
+ *
+ * Caller must lock the mutex.
+ */
+ void CommandWait(audio_output_command cmd);
+
+ /**
+ * Lock the #AudioOutput object and execute the command
+ * synchronously.
+ */
+ void LockCommandWait(audio_output_command cmd);
+
+ /**
+ * Enables the device.
+ */
+ void LockEnableWait();
+
+ /**
+ * Disables the device.
+ */
+ void LockDisableWait();
+
+ void LockPauseAsync();
+
+ /**
+ * Same LockCloseWait(), but expects the lock to be
+ * held by the caller.
+ */
+ void CloseWait();
+ void LockCloseWait();
+
+ /**
+ * Closes the audio output, but if the "always_on" flag is set, put it
+ * into pause mode instead.
+ */
+ void LockRelease();
+
+ void SetReplayGainMode(ReplayGainMode mode);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool Open(const AudioFormat audio_format, const MusicPipe &mp);
+
+ /**
+ * Opens or closes the device, depending on the "enabled"
+ * flag.
+ *
+ * @return true if the device is open
+ */
+ bool LockUpdate(const AudioFormat audio_format,
+ const MusicPipe &mp);
+
+ void LockPlay();
+
+ void LockDrainAsync();
+
+ /**
+ * Clear the "allow_play" flag and send the "CANCEL" command
+ * asynchronously. To finish the operation, the caller has to
+ * call LockAllowPlay().
+ */
+ void LockCancelAsync();
+
+ /**
+ * Set the "allow_play" and signal the thread.
+ */
+ void LockAllowPlay();
+
+private:
+ void CommandFinished();
+
+ bool Enable();
+ void Disable();
+
+ void Open();
+ void Close(bool drain);
+ void Reopen();
+
+ AudioFormat OpenFilter(AudioFormat &format, Error &error_r);
+
+ /**
+ * Mutex must not be locked.
+ */
+ void CloseFilter();
+
+ void ReopenFilter();
+
+ /**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a
+ * command was issued
+ */
+ bool WaitForDelay();
+
+ gcc_pure
+ const MusicChunk *GetNextChunk() const;
+
+ bool PlayChunk(const MusicChunk *chunk);
+
+ /**
+ * 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
+ */
+ bool Play();
+
+ void Pause();
+
+ /**
+ * The OutputThread.
+ */
+ void Task();
+ static void Task(void *arg);
+};
+
+/**
+ * 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;
+
+AudioOutput *
+audio_output_new(EventLoop &event_loop, const config_param &param,
+ MixerListener &mixer_listener,
+ PlayerControl &pc,
+ Error &error);
+
+void
+audio_output_free(AudioOutput *ao);
+
+#endif
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
deleted file mode 100644
index 7ed672f95..000000000
--- a/src/output/JackOutputPlugin.cxx
+++ /dev/null
@@ -1,769 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "JackOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-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, Error &error_r) {
- return ao_base_init(&base, &jack_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain jack_output_domain("jack_output");
-
-/**
- * Determine the number of frames guaranteed to be available on all
- * channels.
- */
-static jack_nframes_t
-mpd_jack_available(const JackOutput *jd)
-{
- 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)
-{
- LogError(jack_output_domain, msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- LogDefault(jack_output_domain, 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, Error &error)
-{
- 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) {
- error.Format(jack_output_domain, status,
- "Failed to connect to JACK server, status=%d",
- status);
- return false;
- }
-
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
- 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) {
- error.Format(jack_output_domain,
- "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(const char *source, char **dest, Error &error)
-{
- char **list = g_strsplit(source, ",", 0);
- unsigned n = 0;
-
- for (n = 0; list[n] != nullptr; ++n) {
- if (n >= MAX_PORTS) {
- error.Set(config_domain,
- "too many port names");
- return 0;
- }
-
- dest[n] = list[n];
- }
-
- g_free(list);
-
- if (n == 0) {
- error.Format(config_domain,
- "at least one port name expected");
- return 0;
- }
-
- return n;
-}
-
-static struct audio_output *
-mpd_jack_init(const config_param &param, Error &error)
-{
- JackOutput *jd = new JackOutput();
-
- if (!jd->Initialize(param, error)) {
- 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(value,
- jd->source_ports, error);
- 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)
- FormatWarning(jack_output_domain,
- "deprecated option 'ports' in line %d",
- param.line);
- }
-
- if (value != nullptr) {
- jd->num_destination_ports =
- parse_port_list(value,
- jd->destination_ports, error);
- 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)
- FormatWarning(jack_output_domain,
- "number of source ports (%u) mismatches the "
- "number of destination ports (%u) in line %d",
- jd->num_source_ports, jd->num_destination_ports,
- 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, Error &error)
-{
- JackOutput *jd = (JackOutput *)ao;
-
- for (unsigned i = 0; i < jd->num_source_ports; ++i)
- jd->ringbuffer[i] = nullptr;
-
- return mpd_jack_connect(jd, error);
-}
-
-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, Error &error)
-{
- const char *destination_ports[MAX_PORTS], **jports;
- const char *duplicate_port = nullptr;
- unsigned num_destination_ports;
-
- assert(jd->client != nullptr);
- assert(jd->audio_format.channels <= jd->num_source_ports);
-
- /* 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) ) {
- error.Set(jack_output_domain, "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) {
- error.Set(jack_output_domain, "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) {
- FormatDebug(jack_output_domain,
- "destination_port[%u] = '%s'\n",
- num_destination_ports,
- jports[num_destination_ports]);
- destination_ports[num_destination_ports] =
- jports[num_destination_ports];
- }
- } 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) {
- error.Format(jack_output_domain,
- "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) {
- error.Format(jack_output_domain,
- "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,
- Error &error)
-{
- 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))
- return false;
-
- set_audioformat(jd, audio_format);
- jd->audio_format = audio_format;
-
- if (!mpd_jack_start(jd, error))
- return false;
-
- return true;
-}
-
-static void
-mpd_jack_close(gcc_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,
- Error &error)
-{
- 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) {
- error.Set(jack_output_domain,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
- }
-
- if (space >= jack_sample_size)
- break;
-
- /* 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
deleted file mode 100644
index 908105ad2..000000000
--- a/src/output/JackOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx
new file mode 100644
index 000000000..33ab57894
--- /dev/null
+++ b/src/output/MultipleOutputs.cxx
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MultipleOutputs.hxx"
+#include "PlayerControl.hxx"
+#include "Internal.hxx"
+#include "Domain.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "system/FatalError.hxx"
+#include "util/Error.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "notify.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+MultipleOutputs::MultipleOutputs(MixerListener &_mixer_listener)
+ :mixer_listener(_mixer_listener),
+ input_audio_format(AudioFormat::Undefined()),
+ buffer(nullptr), pipe(nullptr),
+ elapsed_time(SignedSongTime::Negative())
+{
+}
+
+MultipleOutputs::~MultipleOutputs()
+{
+ for (auto i : outputs) {
+ i->LockDisableWait();
+ i->Finish();
+ }
+}
+
+static AudioOutput *
+LoadOutput(EventLoop &event_loop, MixerListener &mixer_listener,
+ PlayerControl &pc, const config_param &param)
+{
+ Error error;
+ AudioOutput *output = audio_output_new(event_loop, param,
+ mixer_listener,
+ pc, error);
+ if (output == nullptr) {
+ if (param.line > 0)
+ FormatFatalError("line %i: %s",
+ param.line,
+ error.GetMessage());
+ else
+ FatalError(error);
+ }
+
+ return output;
+}
+
+void
+MultipleOutputs::Configure(EventLoop &event_loop, PlayerControl &pc)
+{
+ for (const config_param *param = config_get_param(CONF_AUDIO_OUTPUT);
+ param != nullptr; param = param->next) {
+ auto output = LoadOutput(event_loop, mixer_listener,
+ pc, *param);
+ if (FindByName(output->name) != nullptr)
+ FormatFatalError("output devices with identical "
+ "names: %s", output->name);
+
+ outputs.push_back(output);
+ }
+
+ if (outputs.empty()) {
+ /* auto-detect device */
+ const config_param empty;
+ auto output = LoadOutput(event_loop, mixer_listener,
+ pc, empty);
+ outputs.push_back(output);
+ }
+}
+
+AudioOutput *
+MultipleOutputs::FindByName(const char *name) const
+{
+ for (auto i : outputs)
+ if (strcmp(i->name, name) == 0)
+ return i;
+
+ return nullptr;
+}
+
+void
+MultipleOutputs::EnableDisable()
+{
+ for (auto ao : outputs) {
+ bool enabled;
+
+ ao->mutex.lock();
+ enabled = ao->really_enabled;
+ ao->mutex.unlock();
+
+ if (ao->enabled != enabled) {
+ if (ao->enabled)
+ ao->LockEnableWait();
+ else
+ ao->LockDisableWait();
+ }
+ }
+}
+
+bool
+MultipleOutputs::AllFinished() const
+{
+ for (auto ao : outputs) {
+ const ScopeLock protect(ao->mutex);
+ if (ao->IsOpen() && !ao->IsCommandFinished())
+ return false;
+ }
+
+ return true;
+}
+
+void
+MultipleOutputs::WaitAll()
+{
+ while (!AllFinished())
+ audio_output_client_notify.Wait();
+}
+
+void
+MultipleOutputs::AllowPlay()
+{
+ for (auto ao : outputs)
+ ao->LockAllowPlay();
+}
+
+static void
+audio_output_reset_reopen(AudioOutput *ao)
+{
+ const ScopeLock protect(ao->mutex);
+
+ ao->fail_timer.Reset();
+}
+
+void
+MultipleOutputs::ResetReopen()
+{
+ for (auto ao : outputs)
+ audio_output_reset_reopen(ao);
+}
+
+bool
+MultipleOutputs::Update()
+{
+ bool ret = false;
+
+ if (!input_audio_format.IsDefined())
+ return false;
+
+ for (auto ao : outputs)
+ ret = ao->LockUpdate(input_audio_format, *pipe)
+ || ret;
+
+ return ret;
+}
+
+void
+MultipleOutputs::SetReplayGainMode(ReplayGainMode mode)
+{
+ for (auto ao : outputs)
+ ao->SetReplayGainMode(mode);
+}
+
+bool
+MultipleOutputs::Play(MusicChunk *chunk, Error &error)
+{
+ assert(buffer != nullptr);
+ assert(pipe != nullptr);
+ assert(chunk != nullptr);
+ assert(chunk->CheckFormat(input_audio_format));
+
+ if (!Update()) {
+ /* TODO: obtain real error */
+ error.Set(output_domain, "Failed to open audio output");
+ return false;
+ }
+
+ pipe->Push(chunk);
+
+ for (auto ao : outputs)
+ ao->LockPlay();
+
+ return true;
+}
+
+bool
+MultipleOutputs::Open(const AudioFormat audio_format,
+ MusicBuffer &_buffer,
+ Error &error)
+{
+ bool ret = false, enabled = false;
+
+ assert(buffer == nullptr || buffer == &_buffer);
+ assert((pipe == nullptr) == (buffer == nullptr));
+
+ buffer = &_buffer;
+
+ /* the audio format must be the same as existing chunks in the
+ pipe */
+ assert(pipe == nullptr || pipe->CheckFormat(audio_format));
+
+ if (pipe == nullptr)
+ pipe = new MusicPipe();
+ else
+ /* if the pipe hasn't been cleared, the the audio
+ format must not have changed */
+ assert(pipe->IsEmpty() || audio_format == input_audio_format);
+
+ input_audio_format = audio_format;
+
+ ResetReopen();
+ EnableDisable();
+ Update();
+
+ for (auto ao : outputs) {
+ if (ao->enabled)
+ enabled = true;
+
+ if (ao->open)
+ ret = true;
+ }
+
+ if (!enabled)
+ error.Set(output_domain, "All audio outputs are disabled");
+ else if (!ret)
+ /* TODO: obtain real error */
+ error.Set(output_domain, "Failed to open audio output");
+
+ if (!ret)
+ /* close all devices if there was an error */
+ Close();
+
+ return ret;
+}
+
+/**
+ * Has the specified audio output already consumed this chunk?
+ */
+gcc_pure
+static bool
+chunk_is_consumed_in(const AudioOutput *ao,
+ gcc_unused const MusicPipe *pipe,
+ const MusicChunk *chunk)
+{
+ if (!ao->open)
+ return true;
+
+ if (ao->current_chunk == nullptr)
+ return false;
+
+ assert(chunk == ao->current_chunk ||
+ pipe->Contains(ao->current_chunk));
+
+ if (chunk != ao->current_chunk) {
+ assert(chunk->next != nullptr);
+ return true;
+ }
+
+ return ao->current_chunk_finished && chunk->next == nullptr;
+}
+
+bool
+MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const
+{
+ for (auto ao : outputs) {
+ const ScopeLock protect(ao->mutex);
+ if (!chunk_is_consumed_in(ao, pipe, chunk))
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+MultipleOutputs::ClearTailChunk(gcc_unused const MusicChunk *chunk,
+ bool *locked)
+{
+ assert(chunk->next == nullptr);
+ assert(pipe->Contains(chunk));
+
+ for (unsigned i = 0, n = outputs.size(); i != n; ++i) {
+ AudioOutput *ao = 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->current_chunk == chunk);
+ assert(ao->current_chunk_finished);
+ ao->current_chunk = nullptr;
+ }
+}
+
+unsigned
+MultipleOutputs::Check()
+{
+ const MusicChunk *chunk;
+ bool is_tail;
+ MusicChunk *shifted;
+ bool locked[outputs.size()];
+
+ assert(buffer != nullptr);
+ assert(pipe != nullptr);
+
+ while ((chunk = pipe->Peek()) != nullptr) {
+ assert(!pipe->IsEmpty());
+
+ if (!IsChunkConsumed(chunk))
+ /* at least one output is not finished playing
+ this chunk */
+ return pipe->GetSize();
+
+ if (chunk->length > 0 && !chunk->time.IsNegative())
+ /* only update elapsed_time if the chunk
+ provides a defined value */
+ elapsed_time = chunk->time;
+
+ is_tail = chunk->next == nullptr;
+ if (is_tail)
+ /* this is the tail of the pipe - clear the
+ chunk reference in all outputs */
+ ClearTailChunk(chunk, locked);
+
+ /* remove the chunk from the pipe */
+ shifted = pipe->Shift();
+ assert(shifted == chunk);
+
+ if (is_tail)
+ /* unlock all audio outputs which were locked
+ by clear_tail_chunk() */
+ for (unsigned i = 0, n = outputs.size(); i != n; ++i)
+ if (locked[i])
+ outputs[i]->mutex.unlock();
+
+ /* return the chunk to the buffer */
+ buffer->Return(shifted);
+ }
+
+ return 0;
+}
+
+bool
+MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold)
+{
+ pc.Lock();
+
+ if (Check() < threshold) {
+ pc.Unlock();
+ return true;
+ }
+
+ pc.Wait();
+ pc.Unlock();
+
+ return Check() < threshold;
+}
+
+void
+MultipleOutputs::Pause()
+{
+ Update();
+
+ for (auto ao : outputs)
+ ao->LockPauseAsync();
+
+ WaitAll();
+}
+
+void
+MultipleOutputs::Drain()
+{
+ for (auto ao : outputs)
+ ao->LockDrainAsync();
+
+ WaitAll();
+}
+
+void
+MultipleOutputs::Cancel()
+{
+ /* send the cancel() command to all audio outputs */
+
+ for (auto ao : outputs)
+ ao->LockCancelAsync();
+
+ WaitAll();
+
+ /* clear the music pipe and return all chunks to the buffer */
+
+ if (pipe != nullptr)
+ pipe->Clear(*buffer);
+
+ /* the audio outputs are now waiting for a signal, to
+ synchronize the cleared music pipe */
+
+ AllowPlay();
+
+ /* invalidate elapsed_time */
+
+ elapsed_time = SignedSongTime::Negative();
+}
+
+void
+MultipleOutputs::Close()
+{
+ for (auto ao : outputs)
+ ao->LockCloseWait();
+
+ if (pipe != nullptr) {
+ assert(buffer != nullptr);
+
+ pipe->Clear(*buffer);
+ delete pipe;
+ pipe = nullptr;
+ }
+
+ buffer = nullptr;
+
+ input_audio_format.Clear();
+
+ elapsed_time = SignedSongTime::Negative();
+}
+
+void
+MultipleOutputs::Release()
+{
+ for (auto ao : outputs)
+ ao->LockRelease();
+
+ if (pipe != nullptr) {
+ assert(buffer != nullptr);
+
+ pipe->Clear(*buffer);
+ delete pipe;
+ pipe = nullptr;
+ }
+
+ buffer = nullptr;
+
+ input_audio_format.Clear();
+
+ elapsed_time = SignedSongTime::Negative();
+}
+
+void
+MultipleOutputs::SongBorder()
+{
+ /* clear the elapsed_time pointer at the beginning of a new
+ song */
+ elapsed_time = SignedSongTime::zero();
+}
diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx
new file mode 100644
index 000000000..2c6536e2a
--- /dev/null
+++ b/src/output/MultipleOutputs.hxx
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Functions for dealing with all configured (enabled) audion outputs
+ * at once.
+ *
+ */
+
+#ifndef OUTPUT_ALL_H
+#define OUTPUT_ALL_H
+
+#include "AudioFormat.hxx"
+#include "ReplayGainInfo.hxx"
+#include "Chrono.hxx"
+#include "Compiler.h"
+
+#include <vector>
+
+#include <assert.h>
+
+struct AudioFormat;
+class MusicBuffer;
+class MusicPipe;
+class EventLoop;
+class MixerListener;
+struct MusicChunk;
+struct PlayerControl;
+struct AudioOutput;
+class Error;
+
+class MultipleOutputs {
+ MixerListener &mixer_listener;
+
+ std::vector<AudioOutput *> outputs;
+
+ AudioFormat input_audio_format;
+
+ /**
+ * The #MusicBuffer object where consumed chunks are returned.
+ */
+ MusicBuffer *buffer;
+
+ /**
+ * The #MusicPipe object which feeds all audio outputs. It is
+ * filled by audio_output_all_play().
+ */
+ MusicPipe *pipe;
+
+ /**
+ * The "elapsed_time" stamp of the most recently finished
+ * chunk.
+ */
+ SignedSongTime elapsed_time;
+
+public:
+ /**
+ * Load audio outputs from the configuration file and
+ * initialize them.
+ */
+ MultipleOutputs(MixerListener &_mixer_listener);
+ ~MultipleOutputs();
+
+ void Configure(EventLoop &event_loop, PlayerControl &pc);
+
+ /**
+ * Returns the total number of audio output devices, including
+ * those which are disabled right now.
+ */
+ gcc_pure
+ unsigned Size() const {
+ return outputs.size();
+ }
+
+ /**
+ * Returns the "i"th audio output device.
+ */
+ const AudioOutput &Get(unsigned i) const {
+ assert(i < Size());
+
+ return *outputs[i];
+ }
+
+ AudioOutput &Get(unsigned i) {
+ assert(i < Size());
+
+ return *outputs[i];
+ }
+
+ /**
+ * Returns the audio output device with the specified name.
+ * Returns nullptr if the name does not exist.
+ */
+ gcc_pure
+ AudioOutput *FindByName(const char *name) const;
+
+ /**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+ void EnableDisable();
+
+ /**
+ * Opens all audio outputs which are not disabled.
+ *
+ * @param audio_format the preferred audio format
+ * @param buffer the #music_buffer where consumed #MusicChunk objects
+ * should be returned
+ * @return true on success, false on failure
+ */
+ bool Open(const AudioFormat audio_format, MusicBuffer &_buffer,
+ Error &error);
+
+ /**
+ * Closes all audio outputs.
+ */
+ void Close();
+
+ /**
+ * Closes all audio outputs. Outputs with the "always_on"
+ * flag are put into pause mode.
+ */
+ void Release();
+
+ void SetReplayGainMode(ReplayGainMode mode);
+
+ /**
+ * Enqueue a #MusicChunk object for playing, i.e. pushes it to a
+ * #MusicPipe.
+ *
+ * @param chunk the #MusicChunk object to be played
+ * @return true on success, false if no audio output was able to play
+ * (all closed then)
+ */
+ bool Play(MusicChunk *chunk, Error &error);
+
+ /**
+ * 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 #MusicPipe
+ */
+ unsigned Check();
+
+ /**
+ * Checks if the size of the #MusicPipe is below the #threshold. If
+ * not, it attempts to synchronize with all output threads, and waits
+ * until another #MusicChunk 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 Wait(PlayerControl &pc, unsigned threshold);
+
+ /**
+ * Puts all audio outputs into pause mode. Most implementations will
+ * simply close it then.
+ */
+ void Pause();
+
+ /**
+ * Drain all audio outputs.
+ */
+ void Drain();
+
+ /**
+ * Try to cancel data which may still be in the device's buffers.
+ */
+ void Cancel();
+
+ /**
+ * Indicate that a new song will begin now.
+ */
+ void SongBorder();
+
+ /**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk. A negative value is returned when no chunk has been
+ * finished yet.
+ */
+ gcc_pure
+ SignedSongTime GetElapsedTime() const {
+ return elapsed_time;
+ }
+
+ /**
+ * Returns the average volume of all available mixers (range
+ * 0..100). Returns -1 if no mixer can be queried.
+ */
+ gcc_pure
+ int GetVolume() const;
+
+ /**
+ * Sets the volume on all available mixers.
+ *
+ * @param volume the volume (range 0..100)
+ * @return true on success, false on failure
+ */
+ bool SetVolume(unsigned volume);
+
+ /**
+ * Similar to GetVolume(), but gets the volume only for
+ * software mixers. See #software_mixer_plugin. This
+ * function fails if no software mixer is configured.
+ */
+ gcc_pure
+ int GetSoftwareVolume() const;
+
+ /**
+ * Similar to SetVolume(), 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 SetSoftwareVolume(unsigned volume);
+
+private:
+ /**
+ * Determine if all (active) outputs have finished the current
+ * command.
+ */
+ gcc_pure
+ bool AllFinished() const;
+
+ void WaitAll();
+
+ /**
+ * Signals all audio outputs which are open.
+ */
+ void AllowPlay();
+
+ /**
+ * 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.
+ */
+ void ResetReopen();
+
+ /**
+ * Opens all output devices which are enabled, but closed.
+ *
+ * @return true if there is at least open output device which
+ * is open
+ */
+ bool Update();
+
+ /**
+ * Has this chunk been consumed by all audio outputs?
+ */
+ bool IsChunkConsumed(const MusicChunk *chunk) const;
+
+ /**
+ * There's only one chunk left in the pipe (#pipe), and all
+ * audio outputs have consumed it already. Clear the
+ * reference.
+ */
+ void ClearTailChunk(const MusicChunk *chunk, bool *locked);
+};
+
+#endif
diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx
deleted file mode 100644
index e2eec9dbc..000000000
--- a/src/output/NullOutputPlugin.cxx
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "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, Error &error) {
- return ao_base_init(&base, &null_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static struct audio_output *
-null_init(const config_param &param, Error &error)
-{
- NullOutput *nd = new NullOutput();
-
- if (!nd->Initialize(param, error)) {
- 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 Error &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 Error &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
deleted file mode 100644
index a58f1cb13..000000000
--- a/src/output/NullOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 97ebae056..000000000
--- a/src/output/OSXOutputPlugin.cxx
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OSXOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/fifo_buffer.h"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "thread/Mutex.hxx"
-#include "thread/Cond.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <CoreAudio/AudioHardware.h>
-#include <AudioUnit/AudioUnit.h>
-#include <CoreServices/CoreServices.h>
-
-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;
-};
-
-static constexpr Domain osx_output_domain("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, Error &error)
-{
- OSXOutput *oo = new OSXOutput();
- if (!ao_base_init(&oo->base, &osx_output_plugin, param, error)) {
- 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, Error &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) {
- error.Format(osx_output_domain, 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) {
- error.Format(osx_output_domain, 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) {
- error.Format(osx_output_domain, 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) {
- FormatDebug(osx_output_domain,
- "found matching device: ID=%u, name=%s",
- (unsigned)deviceids[i], name);
- break;
- }
- }
- if (i == numdevices) {
- FormatWarning(osx_output_domain,
- "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) {
- error.Format(osx_output_domain, status,
- "Unable to set OS X audio output device: %s",
- GetMacOSStatusCommentString(status));
- ret = false;
- goto done;
- }
-
- FormatDebug(osx_output_domain,
- "set OS X audio output device ID=%u, name=%s",
- (unsigned)deviceids[i], name);
-
-done:
- delete[] deviceids;
- return ret;
-}
-
-static OSStatus
-osx_render(void *vdata,
- gcc_unused AudioUnitRenderActionFlags *io_action_flags,
- gcc_unused const AudioTimeStamp *in_timestamp,
- gcc_unused UInt32 in_bus_number,
- gcc_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, Error &error)
-{
- 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) {
- error.Set(osx_output_domain,
- "Error finding OS X component");
- return false;
- }
-
- OSStatus status = OpenAComponent(comp, &oo->au);
- if (status != noErr) {
- error.Format(osx_output_domain, status,
- "Unable to open OS X component: %s",
- GetMacOSStatusCommentString(status));
- return false;
- }
-
- if (!osx_output_set_device(oo, error)) {
- 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);
- error.Set(osx_output_domain, 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,
- Error &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 (IsBigEndian())
- stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
-
- 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) {
- error.Set(osx_output_domain, result,
- "Unable to set format on OS X device");
- return false;
- }
-
- OSStatus status = AudioUnitInitialize(od->au);
- if (status != noErr) {
- error.Format(osx_output_domain, 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);
- error.Format(osx_output_domain, 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,
- gcc_unused Error &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
deleted file mode 100644
index 2a4172880..000000000
--- a/src/output/OSXOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 268cf17cc..000000000
--- a/src/output/OpenALOutputPlugin.cxx
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OpenALOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <glib.h>
-
-#ifndef __APPLE__
-#include <AL/al.h>
-#include <AL/alc.h>
-#else
-#include <OpenAL/al.h>
-#include <OpenAL/alc.h>
-#endif
-
-/* 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, Error &error_r) {
- return ao_base_init(&base, &openal_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain openal_output_domain("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);
- }
-}
-
-gcc_pure
-static inline ALint
-openal_get_source_i(const OpenALOutput *od, ALenum param)
-{
- ALint value;
- alGetSourcei(od->source, param, &value);
- return value;
-}
-
-gcc_pure
-static inline bool
-openal_has_processed(const OpenALOutput *od)
-{
- return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
-}
-
-gcc_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, Error &error)
-{
- od->device = alcOpenDevice(od->device_name);
-
- if (od->device == nullptr) {
- error.Format(openal_output_domain,
- "Error opening OpenAL device \"%s\"",
- od->device_name);
- return false;
- }
-
- od->context = alcCreateContext(od->device, nullptr);
-
- if (od->context == nullptr) {
- error.Format(openal_output_domain,
- "Error creating context for \"%s\"",
- od->device_name);
- alcCloseDevice(od->device);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-openal_init(const config_param &param, Error &error)
-{
- 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)) {
- 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,
- Error &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) {
- error.Set(openal_output_domain, "Failed to generate buffers");
- return false;
- }
-
- alGenSources(1, &od->source);
-
- if (alGetError() != AL_NO_ERROR) {
- error.Set(openal_output_domain, "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,
- gcc_unused Error &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
deleted file mode 100644
index e1ebf3d4f..000000000
--- a/src/output/OpenALOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index cdde6d562..000000000
--- a/src/output/OssOutputPlugin.cxx
+++ /dev/null
@@ -1,780 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "OssOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "system/fd_util.h"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-#include "system/ByteOrder.hxx"
-#include "Log.hxx"
-
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <assert.h>
-
-#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, Error &error_r) {
- return ao_base_init(&base, &oss_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-static constexpr Domain oss_output_domain("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 = ARRAY_SIZE(default_devices); --i >= 0; ) {
- fd = open_cloexec(default_devices[i], O_WRONLY, 0);
-
- if (fd >= 0) {
- close(fd);
- return true;
- }
-
- FormatErrno(oss_output_domain,
- "Error opening OSS device \"%s\"",
- default_devices[i]);
- }
-
- return false;
-}
-
-static struct audio_output *
-oss_open_default(Error &error)
-{
- int err[ARRAY_SIZE(default_devices)];
- enum oss_stat ret[ARRAY_SIZE(default_devices)];
-
- const config_param empty;
- for (int i = ARRAY_SIZE(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 = ARRAY_SIZE(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:
- FormatWarning(oss_output_domain,
- "%s not found", dev);
- break;
- case OSS_STAT_NOT_CHAR_DEV:
- FormatWarning(oss_output_domain,
- "%s is not a character device", dev);
- break;
- case OSS_STAT_NO_PERMS:
- FormatWarning(oss_output_domain,
- "%s: permission denied", dev);
- break;
- case OSS_STAT_OTHER:
- FormatErrno(oss_output_domain, err[i],
- "Error accessing %s", dev);
- }
- }
-
- error.Set(oss_output_domain,
- "error trying to open default OSS device");
- return NULL;
-}
-
-static struct audio_output *
-oss_output_init(const config_param &param, Error &error)
-{
- const char *device = param.GetBlockValue("device");
- if (device != NULL) {
- OssOutput *od = new OssOutput();
- if (!od->Initialize(param, error)) {
- delete od;
- return NULL;
- }
-
- od->device = device;
- return &od->base;
- }
-
- return oss_open_default(error);
-}
-
-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, gcc_unused Error &error)
-{
- 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 an #Error.
- */
-static enum oss_setup_result
-oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
- const char *msg, Error &error)
-{
- assert(fd >= 0);
- assert(value_r != NULL);
- assert(msg != NULL);
- assert(!error.IsDefined());
-
- int ret = ioctl(fd, request, value_r);
- if (ret >= 0)
- return SUCCESS;
-
- if (errno == EINVAL)
- return UNSUPPORTED;
-
- error.SetErrno(msg);
- 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 an #Error.
- */
-static enum oss_setup_result
-oss_try_ioctl(int fd, unsigned long request, int value,
- const char *msg, Error &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, Error &error)
-{
- 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);
- 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);
- switch (result) {
- case SUCCESS:
- if (!audio_valid_channel_count(channels))
- break;
-
- audio_format.channels = channels;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- error.Set(oss_output_domain, 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,
- Error &error)
-{
- 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);
- 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);
- 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;
- }
- }
-
- error.Set(oss_output_domain, 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
- Error &error)
-{
- 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);
-
-#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);
- }
-#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 &&
- !IsLittleEndian());
-#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
- Error &error)
-{
- 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);
- 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);
- switch (result) {
- case SUCCESS:
- audio_format.format = mpd_format;
- return true;
-
- case ERROR:
- return false;
-
- case UNSUPPORTED:
- break;
- }
- }
-
- error.Set(oss_output_domain, "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,
- Error &error)
-{
- return oss_setup_channels(od->fd, audio_format, error) &&
- oss_setup_sample_rate(od->fd, audio_format, error) &&
- oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
-#ifdef AFMT_S24_PACKED
- od->pcm_export,
-#endif
- error);
-}
-
-/**
- * Reopen the device with the saved audio_format, without any probing.
- */
-static bool
-oss_reopen(OssOutput *od, Error &error)
-{
- assert(od->fd < 0);
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- error.FormatErrno("Error opening OSS device \"%s\"",
- od->device);
- 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);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- error.Set(oss_output_domain, 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);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- error.Set(oss_output_domain, 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);
- if (result != SUCCESS) {
- oss_close(od);
- if (result == UNSUPPORTED)
- error.Set(oss_output_domain, msg3);
- return false;
- }
-
- return true;
-}
-
-static bool
-oss_output_open(struct audio_output *ao, AudioFormat &audio_format,
- Error &error)
-{
- OssOutput *od = (OssOutput *)ao;
-
- od->fd = open_cloexec(od->device, O_WRONLY, 0);
- if (od->fd < 0) {
- error.FormatErrno("Error opening OSS device \"%s\"",
- od->device);
- 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,
- Error &error)
-{
- OssOutput *od = (OssOutput *)ao;
- ssize_t ret;
-
- assert(size > 0);
-
- /* 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
-
- assert(size > 0);
-
- 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) {
- error.FormatErrno("Write error on %s", od->device);
- 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
deleted file mode 100644
index 6c5c9530b..000000000
--- a/src/output/OssOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/OutputAPI.hxx b/src/output/OutputAPI.hxx
new file mode 100644
index 000000000..e0fd6eec8
--- /dev/null
+++ b/src/output/OutputAPI.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_API_HXX
+#define MPD_OUTPUT_API_HXX
+
+// IWYU pragma: begin_exports
+
+#include "OutputPlugin.hxx"
+#include "Internal.hxx"
+#include "AudioFormat.hxx"
+#include "tag/Tag.hxx"
+#include "config/ConfigData.hxx"
+
+// IWYU pragma: end_exports
+
+#endif
diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx
new file mode 100644
index 000000000..6afb70cf1
--- /dev/null
+++ b/src/output/OutputCommand.cxx
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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 "MultipleOutputs.hxx"
+#include "Internal.hxx"
+#include "PlayerControl.hxx"
+#include "mixer/MixerControl.hxx"
+#include "Idle.hxx"
+
+extern unsigned audio_output_state_version;
+
+bool
+audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
+{
+ if (idx >= outputs.Size())
+ return false;
+
+ AudioOutput &ao = outputs.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(MultipleOutputs &outputs, unsigned idx)
+{
+ if (idx >= outputs.Size())
+ return false;
+
+ AudioOutput &ao = outputs.Get(idx);
+ if (!ao.enabled)
+ return true;
+
+ ao.enabled = false;
+ idle_add(IDLE_OUTPUT);
+
+ Mixer *mixer = ao.mixer;
+ if (mixer != nullptr) {
+ mixer_close(mixer);
+ idle_add(IDLE_MIXER);
+ }
+
+ ao.player_control->UpdateAudio();
+
+ ++audio_output_state_version;
+
+ return true;
+}
+
+bool
+audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
+{
+ if (idx >= outputs.Size())
+ return false;
+
+ AudioOutput &ao = outputs.Get(idx);
+ const bool enabled = ao.enabled = !ao.enabled;
+ idle_add(IDLE_OUTPUT);
+
+ if (!enabled) {
+ Mixer *mixer = ao.mixer;
+ if (mixer != nullptr) {
+ mixer_close(mixer);
+ idle_add(IDLE_MIXER);
+ }
+ }
+
+ ao.player_control->UpdateAudio();
+
+ ++audio_output_state_version;
+
+ return true;
+}
diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx
new file mode 100644
index 000000000..53fc5c95e
--- /dev/null
+++ b/src/output/OutputCommand.hxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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
+
+class MultipleOutputs;
+
+/**
+ * Enables an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_enable_index(MultipleOutputs &outputs, unsigned idx);
+
+/**
+ * Disables an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_disable_index(MultipleOutputs &outputs, unsigned idx);
+
+/**
+ * Toggles an audio output. Returns false if the specified output
+ * does not exist.
+ */
+bool
+audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
+
+#endif
diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx
new file mode 100644
index 000000000..89428fa87
--- /dev/null
+++ b/src/output/OutputControl.cxx
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Internal.hxx"
+#include "OutputPlugin.hxx"
+#include "Domain.hxx"
+#include "mixer/MixerControl.hxx"
+#include "notify.hxx"
+#include "filter/plugins/ReplayGainFilterPlugin.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+/** after a failure, wait this number of seconds before
+ automatically reopening the device */
+static constexpr unsigned REOPEN_AFTER = 10;
+
+struct notify audio_output_client_notify;
+
+void
+AudioOutput::WaitForCommand()
+{
+ while (!IsCommandFinished()) {
+ mutex.unlock();
+ audio_output_client_notify.Wait();
+ mutex.lock();
+ }
+}
+
+void
+AudioOutput::CommandAsync(audio_output_command cmd)
+{
+ assert(IsCommandFinished());
+
+ command = cmd;
+ cond.signal();
+}
+
+void
+AudioOutput::CommandWait(audio_output_command cmd)
+{
+ CommandAsync(cmd);
+ WaitForCommand();
+}
+
+void
+AudioOutput::LockCommandWait(audio_output_command cmd)
+{
+ const ScopeLock protect(mutex);
+ CommandWait(cmd);
+}
+
+void
+AudioOutput::SetReplayGainMode(ReplayGainMode mode)
+{
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter_set_mode(replay_gain_filter, mode);
+ if (other_replay_gain_filter != nullptr)
+ replay_gain_filter_set_mode(other_replay_gain_filter, mode);
+}
+
+void
+AudioOutput::LockEnableWait()
+{
+ if (!thread.IsDefined()) {
+ if (plugin.enable == nullptr) {
+ /* 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 */
+ really_enabled = true;
+ return;
+ }
+
+ StartThread();
+ }
+
+ LockCommandWait(AO_COMMAND_ENABLE);
+}
+
+void
+AudioOutput::LockDisableWait()
+{
+ if (!thread.IsDefined()) {
+ if (plugin.disable == nullptr)
+ really_enabled = false;
+ else
+ /* if there's no thread yet, the device cannot
+ be enabled */
+ assert(!really_enabled);
+
+ return;
+ }
+
+ LockCommandWait(AO_COMMAND_DISABLE);
+}
+
+inline bool
+AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp)
+{
+ assert(allow_play);
+ assert(audio_format.IsValid());
+
+ fail_timer.Reset();
+
+ if (open && audio_format == in_audio_format) {
+ assert(pipe == &mp || (always_on && pause));
+
+ if (pause) {
+ current_chunk = nullptr;
+ 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 */
+ CommandWait(AO_COMMAND_CANCEL);
+ }
+
+ return true;
+ }
+
+ in_audio_format = audio_format;
+ current_chunk = nullptr;
+
+ pipe = &mp;
+
+ if (!thread.IsDefined())
+ StartThread();
+
+ CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
+ const bool open2 = open;
+
+ if (open2 && mixer != nullptr) {
+ Error error;
+ if (!mixer_open(mixer, error))
+ FormatWarning(output_domain,
+ "Failed to open mixer for '%s'", name);
+ }
+
+ return open2;
+}
+
+void
+AudioOutput::CloseWait()
+{
+ assert(allow_play);
+
+ if (mixer != nullptr)
+ mixer_auto_close(mixer);
+
+ assert(!open || !fail_timer.IsDefined());
+
+ if (open)
+ CommandWait(AO_COMMAND_CLOSE);
+ else
+ fail_timer.Reset();
+}
+
+bool
+AudioOutput::LockUpdate(const AudioFormat audio_format,
+ const MusicPipe &mp)
+{
+ const ScopeLock protect(mutex);
+
+ if (enabled && really_enabled) {
+ if (fail_timer.Check(REOPEN_AFTER * 1000)) {
+ return Open(audio_format, mp);
+ }
+ } else if (IsOpen())
+ CloseWait();
+
+ return false;
+}
+
+void
+AudioOutput::LockPlay()
+{
+ const ScopeLock protect(mutex);
+
+ assert(allow_play);
+
+ if (IsOpen() && !in_playback_loop && !woken_for_play) {
+ woken_for_play = true;
+ cond.signal();
+ }
+}
+
+void
+AudioOutput::LockPauseAsync()
+{
+ if (mixer != nullptr && plugin.pause == nullptr)
+ /* the device has no pause mode: close the mixer,
+ unless its "global" flag is set (checked by
+ mixer_auto_close()) */
+ mixer_auto_close(mixer);
+
+ const ScopeLock protect(mutex);
+
+ assert(allow_play);
+ if (IsOpen())
+ CommandAsync(AO_COMMAND_PAUSE);
+}
+
+void
+AudioOutput::LockDrainAsync()
+{
+ const ScopeLock protect(mutex);
+
+ assert(allow_play);
+ if (IsOpen())
+ CommandAsync(AO_COMMAND_DRAIN);
+}
+
+void
+AudioOutput::LockCancelAsync()
+{
+ const ScopeLock protect(mutex);
+
+ if (IsOpen()) {
+ allow_play = false;
+ CommandAsync(AO_COMMAND_CANCEL);
+ }
+}
+
+void
+AudioOutput::LockAllowPlay()
+{
+ const ScopeLock protect(mutex);
+
+ allow_play = true;
+ if (IsOpen())
+ cond.signal();
+}
+
+void
+AudioOutput::LockRelease()
+{
+ if (always_on)
+ LockPauseAsync();
+ else
+ LockCloseWait();
+}
+
+void
+AudioOutput::LockCloseWait()
+{
+ assert(!open || !fail_timer.IsDefined());
+
+ const ScopeLock protect(mutex);
+ CloseWait();
+}
+
+void
+AudioOutput::StopThread()
+{
+ assert(thread.IsDefined());
+ assert(allow_play);
+
+ LockCommandWait(AO_COMMAND_KILL);
+ thread.Join();
+}
+
+void
+AudioOutput::Finish()
+{
+ LockCloseWait();
+
+ assert(!fail_timer.IsDefined());
+
+ if (thread.IsDefined())
+ StopThread();
+
+ audio_output_free(this);
+}
diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx
new file mode 100644
index 000000000..fff3fe406
--- /dev/null
+++ b/src/output/OutputControl.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_CONTROL_HXX
+#define MPD_OUTPUT_CONTROL_HXX
+
+struct AudioOutput;
+
+#endif
diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx
new file mode 100644
index 000000000..33bb854d4
--- /dev/null
+++ b/src/output/OutputPlugin.cxx
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OutputPlugin.hxx"
+#include "Internal.hxx"
+
+AudioOutput *
+ao_plugin_init(const AudioOutputPlugin *plugin,
+ const config_param &param,
+ Error &error)
+{
+ assert(plugin != nullptr);
+ assert(plugin->init != nullptr);
+
+ return plugin->init(param, error);
+}
+
+void
+ao_plugin_finish(AudioOutput *ao)
+{
+ ao->plugin.finish(ao);
+}
+
+bool
+ao_plugin_enable(AudioOutput *ao, Error &error_r)
+{
+ return ao->plugin.enable != nullptr
+ ? ao->plugin.enable(ao, error_r)
+ : true;
+}
+
+void
+ao_plugin_disable(AudioOutput *ao)
+{
+ if (ao->plugin.disable != nullptr)
+ ao->plugin.disable(ao);
+}
+
+bool
+ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ return ao->plugin.open(ao, audio_format, error);
+}
+
+void
+ao_plugin_close(AudioOutput *ao)
+{
+ ao->plugin.close(ao);
+}
+
+unsigned
+ao_plugin_delay(AudioOutput *ao)
+{
+ return ao->plugin.delay != nullptr
+ ? ao->plugin.delay(ao)
+ : 0;
+}
+
+void
+ao_plugin_send_tag(AudioOutput *ao, const Tag *tag)
+{
+ if (ao->plugin.send_tag != nullptr)
+ ao->plugin.send_tag(ao, tag);
+}
+
+size_t
+ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ return ao->plugin.play(ao, chunk, size, error);
+}
+
+void
+ao_plugin_drain(AudioOutput *ao)
+{
+ if (ao->plugin.drain != nullptr)
+ ao->plugin.drain(ao);
+}
+
+void
+ao_plugin_cancel(AudioOutput *ao)
+{
+ if (ao->plugin.cancel != nullptr)
+ ao->plugin.cancel(ao);
+}
+
+bool
+ao_plugin_pause(AudioOutput *ao)
+{
+ return ao->plugin.pause != nullptr && ao->plugin.pause(ao);
+}
diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx
new file mode 100644
index 000000000..00fa36bc0
--- /dev/null
+++ b/src/output/OutputPlugin.hxx
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_PLUGIN_HXX
+#define MPD_OUTPUT_PLUGIN_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+struct config_param;
+struct AudioFormat;
+struct Tag;
+struct AudioOutput;
+struct MixerPlugin;
+class Error;
+
+/**
+ * A plugin which controls an audio output device.
+ */
+struct AudioOutputPlugin {
+ /**
+ * 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 nullptr if there is
+ * no configuration
+ * @return nullptr on error, or an opaque pointer to the plugin's
+ * data
+ */
+ AudioOutput *(*init)(const config_param &param,
+ Error &error);
+
+ /**
+ * Free resources allocated by this device.
+ */
+ void (*finish)(AudioOutput *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.
+ *
+ * @return true on success, false on error
+ */
+ bool (*enable)(AudioOutput *data, Error &error);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(AudioOutput *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
+ */
+ bool (*open)(AudioOutput *data, AudioFormat &audio_format,
+ Error &error);
+
+ /**
+ * Close the device.
+ */
+ void (*close)(AudioOutput *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)(AudioOutput *data);
+
+ /**
+ * Display metadata for the next chunk. Optional method,
+ * because not all devices can display metadata.
+ */
+ void (*send_tag)(AudioOutput *data, const Tag *tag);
+
+ /**
+ * Play a chunk of audio data.
+ *
+ * @return the number of bytes played, or 0 on error
+ */
+ size_t (*play)(AudioOutput *data,
+ const void *chunk, size_t size,
+ Error &error);
+
+ /**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(AudioOutput *data);
+
+ /**
+ * Try to cancel data which may still be in the device's
+ * buffers.
+ */
+ void (*cancel)(AudioOutput *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)(AudioOutput *data);
+
+ /**
+ * The mixer plugin associated with this output plugin. This
+ * may be nullptr if no mixer plugin is implemented. When
+ * created, this mixer plugin gets the same #config_param as
+ * this audio output device.
+ */
+ const MixerPlugin *mixer_plugin;
+};
+
+static inline bool
+ao_plugin_test_default_device(const AudioOutputPlugin *plugin)
+{
+ return plugin->test_default_device != nullptr
+ ? plugin->test_default_device()
+ : false;
+}
+
+gcc_malloc
+AudioOutput *
+ao_plugin_init(const AudioOutputPlugin *plugin,
+ const config_param &param,
+ Error &error);
+
+void
+ao_plugin_finish(AudioOutput *ao);
+
+bool
+ao_plugin_enable(AudioOutput *ao, Error &error);
+
+void
+ao_plugin_disable(AudioOutput *ao);
+
+bool
+ao_plugin_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error);
+
+void
+ao_plugin_close(AudioOutput *ao);
+
+gcc_pure
+unsigned
+ao_plugin_delay(AudioOutput *ao);
+
+void
+ao_plugin_send_tag(AudioOutput *ao, const Tag *tag);
+
+size_t
+ao_plugin_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error);
+
+void
+ao_plugin_drain(AudioOutput *ao);
+
+void
+ao_plugin_cancel(AudioOutput *ao);
+
+bool
+ao_plugin_pause(AudioOutput *ao);
+
+#endif
diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx
new file mode 100644
index 000000000..414a86e32
--- /dev/null
+++ b/src/output/OutputPrint.cxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Protocol specific code for the audio output library.
+ *
+ */
+
+#include "config.h"
+#include "OutputPrint.hxx"
+#include "MultipleOutputs.hxx"
+#include "Internal.hxx"
+#include "client/Client.hxx"
+
+void
+printAudioDevices(Client &client, const MultipleOutputs &outputs)
+{
+ for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
+ const AudioOutput &ao = outputs.Get(i);
+
+ client_printf(client,
+ "outputid: %i\n"
+ "outputname: %s\n"
+ "outputenabled: %i\n",
+ i, ao.name, ao.enabled);
+ }
+}
diff --git a/src/output/OutputPrint.hxx b/src/output/OutputPrint.hxx
new file mode 100644
index 000000000..29aa2b11c
--- /dev/null
+++ b/src/output/OutputPrint.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Protocol specific code for the audio output library.
+ *
+ */
+
+#ifndef MPD_OUTPUT_PRINT_HXX
+#define MPD_OUTPUT_PRINT_HXX
+
+class Client;
+class MultipleOutputs;
+
+void
+printAudioDevices(Client &client, const MultipleOutputs &outputs);
+
+#endif
diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx
new file mode 100644
index 000000000..fb01b1c65
--- /dev/null
+++ b/src/output/OutputState.cxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#include "config.h"
+#include "OutputState.hxx"
+#include "MultipleOutputs.hxx"
+#include "Internal.hxx"
+#include "Domain.hxx"
+#include "Log.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#define AUDIO_DEVICE_STATE "audio_device_state:"
+
+unsigned audio_output_state_version;
+
+void
+audio_output_state_save(BufferedOutputStream &os,
+ const MultipleOutputs &outputs)
+{
+ for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
+ const AudioOutput &ao = outputs.Get(i);
+
+ os.Format(AUDIO_DEVICE_STATE "%d:%s\n", ao.enabled, ao.name);
+ }
+}
+
+bool
+audio_output_state_read(const char *line, MultipleOutputs &outputs)
+{
+ long value;
+ char *endptr;
+ const char *name;
+
+ if (!StringStartsWith(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;
+ AudioOutput *ao = outputs.FindByName(name);
+ if (ao == NULL) {
+ FormatDebug(output_domain,
+ "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/OutputState.hxx b/src/output/OutputState.hxx
new file mode 100644
index 000000000..47f8429d5
--- /dev/null
+++ b/src/output/OutputState.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Saving and loading the audio output states to/from the state file.
+ *
+ */
+
+#ifndef MPD_OUTPUT_STATE_HXX
+#define MPD_OUTPUT_STATE_HXX
+
+class MultipleOutputs;
+class BufferedOutputStream;
+
+bool
+audio_output_state_read(const char *line, MultipleOutputs &outputs);
+
+void
+audio_output_state_save(BufferedOutputStream &os,
+ const MultipleOutputs &outputs);
+
+/**
+ * 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/OutputThread.cxx b/src/output/OutputThread.cxx
new file mode 100644
index 000000000..2ec0670c1
--- /dev/null
+++ b/src/output/OutputThread.cxx
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Internal.hxx"
+#include "OutputAPI.hxx"
+#include "Domain.hxx"
+#include "pcm/PcmMix.hxx"
+#include "pcm/Domain.hxx"
+#include "notify.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/plugins/ConvertFilterPlugin.hxx"
+#include "filter/plugins/ReplayGainFilterPlugin.hxx"
+#include "PlayerControl.hxx"
+#include "MusicPipe.hxx"
+#include "MusicChunk.hxx"
+#include "thread/Util.hxx"
+#include "thread/Slack.hxx"
+#include "thread/Name.hxx"
+#include "system/FatalError.hxx"
+#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
+#include "Log.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+#include <string.h>
+
+void
+AudioOutput::CommandFinished()
+{
+ assert(command != AO_COMMAND_NONE);
+ command = AO_COMMAND_NONE;
+
+ mutex.unlock();
+ audio_output_client_notify.Signal();
+ mutex.lock();
+}
+
+inline bool
+AudioOutput::Enable()
+{
+ if (really_enabled)
+ return true;
+
+ mutex.unlock();
+ Error error;
+ bool success = ao_plugin_enable(this, error);
+ mutex.lock();
+ if (!success) {
+ FormatError(error,
+ "Failed to enable \"%s\" [%s]",
+ name, plugin.name);
+ return false;
+ }
+
+ really_enabled = true;
+ return true;
+}
+
+inline void
+AudioOutput::Disable()
+{
+ if (open)
+ Close(false);
+
+ if (really_enabled) {
+ really_enabled = false;
+
+ mutex.unlock();
+ ao_plugin_disable(this);
+ mutex.lock();
+ }
+}
+
+inline AudioFormat
+AudioOutput::OpenFilter(AudioFormat &format, Error &error_r)
+{
+ assert(format.IsValid());
+
+ /* the replay_gain filter cannot fail here */
+ if (replay_gain_filter != nullptr &&
+ !replay_gain_filter->Open(format, error_r).IsDefined())
+ return AudioFormat::Undefined();
+
+ if (other_replay_gain_filter != nullptr &&
+ !other_replay_gain_filter->Open(format, error_r).IsDefined()) {
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ const AudioFormat af = filter->Open(format, error_r);
+ if (!af.IsDefined()) {
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter->Close();
+ if (other_replay_gain_filter != nullptr)
+ other_replay_gain_filter->Close();
+ }
+
+ return af;
+}
+
+void
+AudioOutput::CloseFilter()
+{
+ if (replay_gain_filter != nullptr)
+ replay_gain_filter->Close();
+ if (other_replay_gain_filter != nullptr)
+ other_replay_gain_filter->Close();
+
+ filter->Close();
+}
+
+inline void
+AudioOutput::Open()
+{
+ bool success;
+ Error error;
+ struct audio_format_string af_string;
+
+ assert(!open);
+ assert(pipe != nullptr);
+ assert(current_chunk == nullptr);
+ assert(in_audio_format.IsValid());
+
+ fail_timer.Reset();
+
+ /* enable the device (just in case the last enable has failed) */
+
+ if (!Enable())
+ /* still no luck */
+ return;
+
+ /* open the filter */
+
+ const AudioFormat filter_audio_format =
+ OpenFilter(in_audio_format, error);
+ if (!filter_audio_format.IsDefined()) {
+ FormatError(error, "Failed to open filter for \"%s\" [%s]",
+ name, plugin.name);
+
+ fail_timer.Update();
+ return;
+ }
+
+ assert(filter_audio_format.IsValid());
+
+ out_audio_format = filter_audio_format;
+ out_audio_format.ApplyMask(config_audio_format);
+
+ mutex.unlock();
+
+ const AudioFormat retry_audio_format = out_audio_format;
+
+ retry_without_dsd:
+ success = ao_plugin_open(this, out_audio_format, error);
+ mutex.lock();
+
+ assert(!open);
+
+ if (!success) {
+ FormatError(error, "Failed to open \"%s\" [%s]",
+ name, plugin.name);
+
+ mutex.unlock();
+ CloseFilter();
+ mutex.lock();
+
+ fail_timer.Update();
+ return;
+ }
+
+ if (!convert_filter_set(convert_filter, out_audio_format,
+ error)) {
+ FormatError(error, "Failed to convert for \"%s\" [%s]",
+ name, plugin.name);
+
+ mutex.unlock();
+ ao_plugin_close(this);
+
+ if (error.IsDomain(pcm_domain) &&
+ out_audio_format.format == SampleFormat::DSD) {
+ /* if the audio output supports DSD, but not
+ the given sample rate, it asks MPD to
+ resample; resampling DSD however is not
+ implemented; our last resort is to give up
+ DSD and fall back to PCM */
+
+ // TODO: clean up this workaround
+
+ FormatError(output_domain, "Retrying without DSD");
+
+ out_audio_format = retry_audio_format;
+ out_audio_format.format = SampleFormat::FLOAT;
+
+ /* clear the Error to allow reusing it */
+ error.Clear();
+
+ /* sorry for the "goto" - this is a workaround
+ for the stable branch that should be as
+ unintrusive as possible */
+ goto retry_without_dsd;
+ }
+
+ CloseFilter();
+ mutex.lock();
+
+ fail_timer.Update();
+ return;
+ }
+
+ open = true;
+
+ FormatDebug(output_domain,
+ "opened plugin=%s name=\"%s\" audio_format=%s",
+ plugin.name, name,
+ audio_format_to_string(out_audio_format, &af_string));
+
+ if (in_audio_format != out_audio_format)
+ FormatDebug(output_domain, "converting from %s",
+ audio_format_to_string(in_audio_format,
+ &af_string));
+}
+
+void
+AudioOutput::Close(bool drain)
+{
+ assert(open);
+
+ pipe = nullptr;
+
+ current_chunk = nullptr;
+ open = false;
+
+ mutex.unlock();
+
+ if (drain)
+ ao_plugin_drain(this);
+ else
+ ao_plugin_cancel(this);
+
+ ao_plugin_close(this);
+ CloseFilter();
+
+ mutex.lock();
+
+ FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
+ plugin.name, name);
+}
+
+void
+AudioOutput::ReopenFilter()
+{
+ Error error;
+
+ mutex.unlock();
+ CloseFilter();
+ mutex.lock();
+
+ const AudioFormat filter_audio_format =
+ OpenFilter(in_audio_format, error);
+ if (!filter_audio_format.IsDefined() ||
+ !convert_filter_set(convert_filter, out_audio_format,
+ error)) {
+ FormatError(error,
+ "Failed to open filter for \"%s\" [%s]",
+ name, plugin.name);
+
+ /* this is a little code duplication from Close(),
+ but we cannot call this function because we must
+ not call filter_close(filter) again */
+
+ pipe = nullptr;
+
+ current_chunk = nullptr;
+ open = false;
+ fail_timer.Update();
+
+ mutex.unlock();
+ ao_plugin_close(this);
+ mutex.lock();
+
+ return;
+ }
+}
+
+void
+AudioOutput::Reopen()
+{
+ if (!config_audio_format.IsFullyDefined()) {
+ if (open) {
+ const MusicPipe *mp = pipe;
+ Close(true);
+ pipe = mp;
+ }
+
+ /* no audio format is configured: copy in->out, let
+ the output's open() method determine the effective
+ out_audio_format */
+ out_audio_format = in_audio_format;
+ out_audio_format.ApplyMask(config_audio_format);
+ }
+
+ if (open)
+ /* the audio format has changed, and all filters have
+ to be reconfigured */
+ ReopenFilter();
+ else
+ Open();
+}
+
+/**
+ * Wait until the output's delay reaches zero.
+ *
+ * @return true if playback should be continued, false if a command
+ * was issued
+ */
+inline bool
+AudioOutput::WaitForDelay()
+{
+ while (true) {
+ unsigned delay = ao_plugin_delay(this);
+ if (delay == 0)
+ return true;
+
+ (void)cond.timed_wait(mutex, delay);
+
+ if (command != AO_COMMAND_NONE)
+ return false;
+ }
+}
+
+static ConstBuffer<void>
+ao_chunk_data(AudioOutput *ao, const MusicChunk *chunk,
+ Filter *replay_gain_filter,
+ unsigned *replay_gain_serial_p)
+{
+ assert(chunk != nullptr);
+ assert(!chunk->IsEmpty());
+ assert(chunk->CheckFormat(ao->in_audio_format));
+
+ ConstBuffer<void> data(chunk->data, chunk->length);
+
+ (void)ao;
+
+ assert(data.size % ao->in_audio_format.GetFrameSize() == 0);
+
+ if (!data.IsEmpty() && replay_gain_filter != nullptr) {
+ 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
+ : nullptr);
+ *replay_gain_serial_p = chunk->replay_gain_serial;
+ }
+
+ Error error;
+ data = replay_gain_filter->FilterPCM(data, error);
+ if (data.IsNull())
+ FormatError(error, "\"%s\" [%s] failed to filter",
+ ao->name, ao->plugin.name);
+ }
+
+ return data;
+}
+
+static ConstBuffer<void>
+ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
+{
+ ConstBuffer<void> data =
+ ao_chunk_data(ao, chunk, ao->replay_gain_filter,
+ &ao->replay_gain_serial);
+ if (data.IsEmpty())
+ return data;
+
+ /* cross-fade */
+
+ if (chunk->other != nullptr) {
+ ConstBuffer<void> other_data =
+ ao_chunk_data(ao, chunk->other,
+ ao->other_replay_gain_filter,
+ &ao->other_replay_gain_serial);
+ if (other_data.IsNull())
+ return nullptr;
+
+ if (other_data.IsEmpty())
+ 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 (data.size > other_data.size)
+ data.size = other_data.size;
+
+ float mix_ratio = chunk->mix_ratio;
+ if (mix_ratio >= 0)
+ /* reverse the mix ratio (because the
+ arguments to pcm_mix() are reversed), but
+ only if the mix ratio is non-negative; a
+ negative mix ratio is a MixRamp special
+ case */
+ mix_ratio = 1.0 - mix_ratio;
+
+ void *dest = ao->cross_fade_buffer.Get(other_data.size);
+ memcpy(dest, other_data.data, other_data.size);
+ if (!pcm_mix(ao->cross_fade_dither, dest, data.data, data.size,
+ ao->in_audio_format.format,
+ mix_ratio)) {
+ FormatError(output_domain,
+ "Cannot cross-fade format %s",
+ sample_format_to_string(ao->in_audio_format.format));
+ return nullptr;
+ }
+
+ data.data = dest;
+ data.size = other_data.size;
+ }
+
+ /* apply filter chain */
+
+ Error error;
+ data = ao->filter->FilterPCM(data, error);
+ if (data.IsNull()) {
+ FormatError(error, "\"%s\" [%s] failed to filter",
+ ao->name, ao->plugin.name);
+ return nullptr;
+ }
+
+ return data;
+}
+
+inline bool
+AudioOutput::PlayChunk(const MusicChunk *chunk)
+{
+ assert(filter != nullptr);
+
+ if (tags && gcc_unlikely(chunk->tag != nullptr)) {
+ mutex.unlock();
+ ao_plugin_send_tag(this, chunk->tag);
+ mutex.lock();
+ }
+
+ auto data = ConstBuffer<char>::FromVoid(ao_filter_chunk(this, chunk));
+ if (data.IsNull()) {
+ Close(false);
+
+ /* don't automatically reopen this device for 10
+ seconds */
+ fail_timer.Update();
+ return false;
+ }
+
+ Error error;
+
+ while (!data.IsEmpty() && command == AO_COMMAND_NONE) {
+ if (!WaitForDelay())
+ break;
+
+ mutex.unlock();
+ size_t nbytes = ao_plugin_play(this, data.data, data.size,
+ error);
+ mutex.lock();
+ if (nbytes == 0) {
+ /* play()==0 means failure */
+ FormatError(error, "\"%s\" [%s] failed to play",
+ name, plugin.name);
+
+ Close(false);
+
+ /* don't automatically reopen this device for
+ 10 seconds */
+ assert(!fail_timer.IsDefined());
+ fail_timer.Update();
+
+ return false;
+ }
+
+ assert(nbytes <= data.size);
+ assert(nbytes % out_audio_format.GetFrameSize() == 0);
+
+ data.data += nbytes;
+ data.size -= nbytes;
+ }
+
+ return true;
+}
+
+inline const MusicChunk *
+AudioOutput::GetNextChunk() const
+{
+ return current_chunk != nullptr
+ /* continue the previous play() call */
+ ? current_chunk->next
+ /* get the first chunk from the pipe */
+ : pipe->Peek();
+}
+
+inline bool
+AudioOutput::Play()
+{
+ assert(pipe != nullptr);
+
+ const MusicChunk *chunk = GetNextChunk();
+ if (chunk == nullptr)
+ /* no chunk available */
+ return false;
+
+ current_chunk_finished = false;
+
+ assert(!in_playback_loop);
+ in_playback_loop = true;
+
+ while (chunk != nullptr && command == AO_COMMAND_NONE) {
+ assert(!current_chunk_finished);
+
+ current_chunk = chunk;
+
+ if (!PlayChunk(chunk)) {
+ assert(current_chunk == nullptr);
+ break;
+ }
+
+ assert(current_chunk == chunk);
+ chunk = chunk->next;
+ }
+
+ assert(in_playback_loop);
+ in_playback_loop = false;
+
+ current_chunk_finished = true;
+
+ mutex.unlock();
+ player_control->LockSignal();
+ mutex.lock();
+
+ return true;
+}
+
+inline void
+AudioOutput::Pause()
+{
+ mutex.unlock();
+ ao_plugin_cancel(this);
+ mutex.lock();
+
+ pause = true;
+ CommandFinished();
+
+ do {
+ if (!WaitForDelay())
+ break;
+
+ mutex.unlock();
+ bool success = ao_plugin_pause(this);
+ mutex.lock();
+
+ if (!success) {
+ Close(false);
+ break;
+ }
+ } while (command == AO_COMMAND_NONE);
+
+ pause = false;
+}
+
+inline void
+AudioOutput::Task()
+{
+ FormatThreadName("output:%s", name);
+
+ SetThreadRealtime();
+ SetThreadTimerSlackUS(100);
+
+ mutex.lock();
+
+ while (1) {
+ switch (command) {
+ case AO_COMMAND_NONE:
+ break;
+
+ case AO_COMMAND_ENABLE:
+ Enable();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_DISABLE:
+ Disable();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_OPEN:
+ Open();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_REOPEN:
+ Reopen();
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_CLOSE:
+ assert(open);
+ assert(pipe != nullptr);
+
+ Close(false);
+ CommandFinished();
+ break;
+
+ case AO_COMMAND_PAUSE:
+ if (!open) {
+ /* the output has failed after
+ audio_output_all_pause() has
+ submitted the PAUSE command; bail
+ out */
+ CommandFinished();
+ break;
+ }
+
+ Pause();
+ /* don't "break" here: this might cause
+ Play() to be called when command==CLOSE
+ ends the paused state - "continue" checks
+ the new command first */
+ continue;
+
+ case AO_COMMAND_DRAIN:
+ if (open) {
+ assert(current_chunk == nullptr);
+ assert(pipe->Peek() == nullptr);
+
+ mutex.unlock();
+ ao_plugin_drain(this);
+ mutex.lock();
+ }
+
+ CommandFinished();
+ continue;
+
+ case AO_COMMAND_CANCEL:
+ current_chunk = nullptr;
+
+ if (open) {
+ mutex.unlock();
+ ao_plugin_cancel(this);
+ mutex.lock();
+ }
+
+ CommandFinished();
+ continue;
+
+ case AO_COMMAND_KILL:
+ current_chunk = nullptr;
+ CommandFinished();
+ mutex.unlock();
+ return;
+ }
+
+ if (open && allow_play && Play())
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
+
+ if (command == AO_COMMAND_NONE) {
+ woken_for_play = false;
+ cond.wait(mutex);
+ }
+ }
+}
+
+void
+AudioOutput::Task(void *arg)
+{
+ AudioOutput *ao = (AudioOutput *)arg;
+ ao->Task();
+}
+
+void
+AudioOutput::StartThread()
+{
+ assert(command == AO_COMMAND_NONE);
+
+ Error error;
+ if (!thread.Start(Task, this, error))
+ FatalError(error);
+}
diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx
deleted file mode 100644
index 34d615284..000000000
--- a/src/output/PipeOutputPlugin.cxx
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PipeOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-
-#include <string>
-
-#include <stdio.h>
-
-struct PipeOutput {
- struct audio_output base;
-
- std::string cmd;
- FILE *fh;
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &pipe_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static constexpr Domain pipe_output_domain("pipe_output");
-
-inline bool
-PipeOutput::Configure(const config_param &param, Error &error)
-{
- cmd = param.GetBlockValue("command", "");
- if (cmd.empty()) {
- error.Set(config_domain,
- "No \"command\" parameter specified");
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-pipe_output_init(const config_param &param, Error &error)
-{
- PipeOutput *pd = new PipeOutput();
-
- if (!pd->Initialize(param, error)) {
- delete pd;
- return nullptr;
- }
-
- if (!pd->Configure(param, error)) {
- pd->Deinitialize();
- delete pd;
- return nullptr;
- }
-
- return &pd->base;
-}
-
-static void
-pipe_output_finish(struct audio_output *ao)
-{
- PipeOutput *pd = (PipeOutput *)ao;
-
- pd->Deinitialize();
- delete pd;
-}
-
-static bool
-pipe_output_open(struct audio_output *ao,
- gcc_unused AudioFormat &audio_format,
- Error &error)
-{
- PipeOutput *pd = (PipeOutput *)ao;
-
- pd->fh = popen(pd->cmd.c_str(), "w");
- if (pd->fh == nullptr) {
- error.FormatErrno("Error opening pipe \"%s\"",
- pd->cmd.c_str());
- 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,
- Error &error)
-{
- PipeOutput *pd = (PipeOutput *)ao;
- size_t ret;
-
- ret = fwrite(chunk, 1, size, pd->fh);
- if (ret == 0)
- error.SetErrno("Write error on pipe");
-
- 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
deleted file mode 100644
index f0c29706b..000000000
--- a/src/output/PipeOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 1eece448a..000000000
--- a/src/output/PulseOutputPlugin.cxx
+++ /dev/null
@@ -1,887 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PulseOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "MixerList.hxx"
-#include "mixer/PulseMixerPlugin.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.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"
-
-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;
-};
-
-static constexpr Domain pulse_output_domain("pulse_output");
-
-static void
-SetError(Error &error, pa_context *context, const char *msg)
-{
- const int e = pa_context_errno(context);
- error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e));
-}
-
-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 pa_cvolume *volume,
- Error &error)
-{
- pa_operation *o;
-
- if (po->context == nullptr || po->stream == nullptr ||
- pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- error.Set(pulse_output_domain, "disconnected");
- return false;
- }
-
- o = pa_context_set_sink_input_volume(po->context,
- pa_stream_get_index(po->stream),
- volume, nullptr, nullptr);
- if (o == nullptr) {
- SetError(error, po->context,
- "failed to set PulseAudio volume");
- 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(gcc_unused pa_stream *s,
- gcc_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, Error &error)
-{
- assert(po != nullptr);
- assert(po->context != nullptr);
-
- if (pa_context_connect(po->context, po->server,
- (pa_context_flags_t)0, nullptr) < 0) {
- SetError(error, po->context,
- "pa_context_connect() has failed");
- return false;
- }
-
- return true;
-}
-
-/**
- * Frees and clears the stream.
- */
-static void
-pulse_output_delete_stream(PulseOutput *po)
-{
- assert(po != nullptr);
- assert(po->stream != nullptr);
-
- pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
-
- 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, Error &error)
-{
- 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) {
- error.Set(pulse_output_domain, "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)) {
- pulse_output_delete_context(po);
- return false;
- }
-
- return true;
-}
-
-static struct audio_output *
-pulse_output_init(const config_param &param, Error &error)
-{
- PulseOutput *po;
-
- g_setenv("PULSE_PROP_media.role", "music", true);
-
- po = new PulseOutput();
- if (!ao_base_init(&po->base, &pulse_output_plugin, param, error)) {
- 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, Error &error)
-{
- 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) {
- error.Set(pulse_output_domain,
- "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;
-
- error.Set(pulse_output_domain,
- "pa_threaded_mainloop_start() has failed");
- return false;
- }
-
- /* create the libpulse context and connect it */
-
- if (!pulse_output_setup_context(po, error)) {
- 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, Error &error)
-{
- assert(po->mainloop != nullptr);
-
- pa_context_state_t state;
-
- if (po->context == nullptr && !pulse_output_setup_context(po, error))
- 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 */
- SetError(error, po->context, "failed to connect");
- 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;
- }
- }
-}
-
-static void
-pulse_output_stream_suspended_cb(gcc_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);
-}
-
-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(gcc_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,
- Error &error)
-{
- assert(po != nullptr);
- assert(po->context != nullptr);
-
- po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
- if (po->stream == nullptr) {
- SetError(error, po->context, "pa_stream_new() has failed");
- return false;
- }
-
- pa_stream_set_suspended_callback(po->stream,
- pulse_output_stream_suspended_cb, po);
-
- 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,
- Error &error)
-{
- PulseOutput *po = (PulseOutput *)ao;
- pa_sample_spec ss;
-
- 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)) {
- 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)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- /* .. and connect it (asynchronously) */
-
- if (pa_stream_connect_playback(po->stream, po->sink,
- nullptr, pa_stream_flags_t(0),
- nullptr, nullptr) < 0) {
- pulse_output_delete_stream(po);
-
- SetError(error, po->context,
- "pa_stream_connect_playback() has failed");
- pa_threaded_mainloop_unlock(po->mainloop);
- return false;
- }
-
- pa_threaded_mainloop_unlock(po->mainloop);
-
- 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) {
- FormatWarning(pulse_output_domain,
- "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, Error &error)
-{
- 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:
- SetError(error, po->context,
- "failed to connect the stream");
- return false;
-
- case PA_STREAM_CREATING:
- pa_threaded_mainloop_wait(po->mainloop);
- break;
- }
- }
-}
-
-/**
- * Sets cork mode on the stream.
- */
-static bool
-pulse_output_stream_pause(PulseOutput *po, bool pause,
- Error &error)
-{
- 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) {
- SetError(error, po->context, "pa_stream_cork() has failed");
- return false;
- }
-
- if (!pulse_wait_for_operation(po->mainloop, o)) {
- SetError(error, po->context, "pa_stream_cork() has failed");
- return false;
- }
-
- 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 && pa_stream_is_corked(po->stream) &&
- 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,
- Error &error)
-{
- PulseOutput *po = (PulseOutput *)ao;
-
- 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)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- assert(po->context != nullptr);
-
- /* unpause if previously paused */
-
- if (pa_stream_is_corked(po->stream) &&
- !pulse_output_stream_pause(po, false, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- return 0;
- }
-
- /* wait until the server allows us to write */
-
- while (po->writable == 0) {
- if (pa_stream_is_suspended(po->stream)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- error.Set(pulse_output_domain, "suspended");
- return 0;
- }
-
- pa_threaded_mainloop_wait(po->mainloop);
-
- if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
- pa_threaded_mainloop_unlock(po->mainloop);
- error.Set(pulse_output_domain, "disconnected");
- return 0;
- }
- }
-
- /* now write */
-
- if (size > po->writable)
- /* don't send more than possible */
- size = po->writable;
-
- po->writable -= size;
-
- int result = pa_stream_write(po->stream, chunk, size, nullptr,
- 0, PA_SEEK_RELATIVE);
- pa_threaded_mainloop_unlock(po->mainloop);
- if (result < 0) {
- SetError(error, po->context, "pa_stream_write() failed");
- 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) {
- FormatWarning(pulse_output_domain,
- "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;
-
- assert(po->mainloop != nullptr);
- assert(po->stream != nullptr);
-
- pa_threaded_mainloop_lock(po->mainloop);
-
- /* check if the stream is (already/still) connected */
-
- Error error;
- if (!pulse_output_wait_stream(po, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- LogError(error);
- return false;
- }
-
- assert(po->context != nullptr);
-
- /* cork the stream */
-
- if (!pa_stream_is_corked(po->stream) &&
- !pulse_output_stream_pause(po, true, error)) {
- pa_threaded_mainloop_unlock(po->mainloop);
- LogError(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, IgnoreError());
- if (po == nullptr)
- return false;
-
- success = pulse_output_wait_connection(po, IgnoreError());
- 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
deleted file mode 100644
index 0ed8404bc..000000000
--- a/src/output/PulseOutputPlugin.hxx
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX
-#define MPD_PULSE_OUTPUT_PLUGIN_HXX
-
-struct PulseOutput;
-struct PulseMixer;
-struct pa_cvolume;
-class Error;
-
-extern const struct audio_output_plugin pulse_output_plugin;
-
-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, Error &error);
-
-#endif
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx
deleted file mode 100644
index 9a7eba01f..000000000
--- a/src/output/RecorderOutputPlugin.cxx
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RecorderOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/fd_util.h"
-#include "open.h"
-
-#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <errno.h>
-
-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, Error &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, Error &error);
-
- bool WriteToFile(const void *data, size_t length, Error &error);
-
- /**
- * Writes pending data from the encoder to the output file.
- */
- bool EncoderToFile(Error &error);
-};
-
-static constexpr Domain recorder_output_domain("recorder_output");
-
-inline bool
-RecorderOutput::Configure(const config_param &param, Error &error)
-{
- /* read configuration */
-
- const char *encoder_name =
- param.GetBlockValue("encoder", "vorbis");
- const auto encoder_plugin = encoder_plugin_get(encoder_name);
- if (encoder_plugin == nullptr) {
- error.Format(config_domain,
- "No such encoder: %s", encoder_name);
- return false;
- }
-
- path = param.GetBlockValue("path");
- if (path == nullptr) {
- error.Set(config_domain, "'path' not configured");
- return false;
- }
-
- /* initialize encoder */
-
- encoder = encoder_init(*encoder_plugin, param, error);
- if (encoder == nullptr)
- return false;
-
- return true;
-}
-
-static audio_output *
-recorder_output_init(const config_param &param, Error &error)
-{
- RecorderOutput *recorder = new RecorderOutput();
-
- if (!recorder->Initialize(param, error)) {
- delete recorder;
- return nullptr;
- }
-
- if (!recorder->Configure(param, error)) {
- 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, Error &error)
-{
- 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 */
- error.Set(recorder_output_domain,
- "write() returned 0");
- return false;
- } else if (errno != EINTR) {
- error.FormatErrno("Failed to write to '%s'", path);
- return false;
- }
- }
-}
-
-inline bool
-RecorderOutput::EncoderToFile(Error &error)
-{
- 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))
- return false;
- }
-}
-
-static bool
-recorder_output_open(struct audio_output *ao,
- AudioFormat &audio_format,
- Error &error)
-{
- 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) {
- error.FormatErrno("Failed to create '%s'", recorder->path);
- return false;
- }
-
- /* open the encoder */
-
- if (!encoder_open(recorder->encoder, audio_format, error)) {
- close(recorder->fd);
- unlink(recorder->path);
- return false;
- }
-
- if (!recorder->EncoderToFile(error)) {
- 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, IgnoreError()))
- recorder->EncoderToFile(IgnoreError());
-
- /* 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,
- Error &error)
-{
- RecorderOutput *recorder = (RecorderOutput *)ao;
-
- return encoder_write(recorder->encoder, chunk, size, error) &&
- recorder->EncoderToFile(error)
- ? 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
deleted file mode 100644
index a27f51e23..000000000
--- a/src/output/RecorderOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/Registry.cxx b/src/output/Registry.cxx
new file mode 100644
index 000000000..566f6b6a8
--- /dev/null
+++ b/src/output/Registry.cxx
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Registry.hxx"
+#include "OutputAPI.hxx"
+#include "plugins/AlsaOutputPlugin.hxx"
+#include "plugins/AoOutputPlugin.hxx"
+#include "plugins/FifoOutputPlugin.hxx"
+#include "plugins/httpd/HttpdOutputPlugin.hxx"
+#include "plugins/JackOutputPlugin.hxx"
+#include "plugins/NullOutputPlugin.hxx"
+#include "plugins/OpenALOutputPlugin.hxx"
+#include "plugins/OssOutputPlugin.hxx"
+#include "plugins/OSXOutputPlugin.hxx"
+#include "plugins/PipeOutputPlugin.hxx"
+#include "plugins/PulseOutputPlugin.hxx"
+#include "plugins/RecorderOutputPlugin.hxx"
+#include "plugins/RoarOutputPlugin.hxx"
+#include "plugins/ShoutOutputPlugin.hxx"
+#include "plugins/sles/SlesOutputPlugin.hxx"
+#include "plugins/SolarisOutputPlugin.hxx"
+#include "plugins/WinmmOutputPlugin.hxx"
+
+#include <string.h>
+
+const AudioOutputPlugin *const audio_output_plugins[] = {
+#ifdef HAVE_SHOUT
+ &shout_output_plugin,
+#endif
+ &null_output_plugin,
+#ifdef ANDROID
+ &sles_output_plugin,
+#endif
+#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
+ nullptr
+};
+
+const AudioOutputPlugin *
+AudioOutputPlugin_get(const char *name)
+{
+ audio_output_plugins_for_each(plugin)
+ if (strcmp(plugin->name, name) == 0)
+ return plugin;
+
+ return nullptr;
+}
diff --git a/src/output/Registry.hxx b/src/output/Registry.hxx
new file mode 100644
index 000000000..bc9c1ae2b
--- /dev/null
+++ b/src/output/Registry.hxx
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_LIST_HXX
+#define MPD_OUTPUT_LIST_HXX
+
+struct AudioOutputPlugin;
+
+extern const AudioOutputPlugin *const audio_output_plugins[];
+
+const AudioOutputPlugin *
+AudioOutputPlugin_get(const char *name);
+
+#define audio_output_plugins_for_each(plugin) \
+ for (const AudioOutputPlugin *plugin, \
+ *const*output_plugin_iterator = &audio_output_plugins[0]; \
+ (plugin = *output_plugin_iterator) != nullptr; ++output_plugin_iterator)
+
+#endif
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
deleted file mode 100644
index 20d69f3f9..000000000
--- a/src/output/RoarOutputPlugin.cxx
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * 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 "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <string>
-
-/* libroar/services.h declares roar_service_stream::new - work around
- this C++ problem */
-#define new _new
-#include <roaraudio.h>
-#undef new
-
-class RoarOutput {
- struct audio_output base;
-
- std::string host, name;
-
- roar_vs_t * vss;
- int err;
- int role;
- struct roar_connection con;
- struct roar_audio_info info;
- mutable Mutex mutex;
- bool alive;
-
-public:
- RoarOutput()
- :err(ROAR_ERROR_NONE) {}
-
- operator audio_output *() {
- return &base;
- }
-
- bool Initialize(const config_param &param, Error &error) {
- return ao_base_init(&base, &roar_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- void Configure(const config_param &param);
-
- bool Open(AudioFormat &audio_format, Error &error);
- void Close();
-
- void SendTag(const Tag &tag);
- size_t Play(const void *chunk, size_t size, Error &error);
- void Cancel();
-
- int GetVolume() const;
- bool SetVolume(unsigned volume);
-};
-
-static constexpr Domain roar_output_domain("roar_output");
-
-inline int
-RoarOutput::GetVolume() const
-{
- const ScopeLock protect(mutex);
-
- if (vss == nullptr || !alive)
- return -1;
-
- float l, r;
- int error;
- if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
- return -1;
-
- return (l + r) * 50;
-}
-
-int
-roar_output_get_volume(RoarOutput *roar)
-{
- return roar->GetVolume();
-}
-
-bool
-RoarOutput::SetVolume(unsigned volume)
-{
- assert(volume <= 100);
-
- const ScopeLock protect(mutex);
- if (vss == nullptr || !alive)
- return false;
-
- int error;
- float level = volume / 100.0;
-
- roar_vs_volume_mono(vss, level, &error);
- return true;
-}
-
-bool
-roar_output_set_volume(RoarOutput *roar, unsigned volume)
-{
- return roar->SetVolume(volume);
-}
-
-inline void
-RoarOutput::Configure(const config_param &param)
-{
- host = param.GetBlockValue("server", "");
- name = param.GetBlockValue("name", "MPD");
-
- const char *_role = param.GetBlockValue("role", "music");
- role = _role != nullptr
- ? roar_str2role(_role)
- : ROAR_ROLE_MUSIC;
-}
-
-static struct audio_output *
-roar_init(const config_param &param, Error &error)
-{
- RoarOutput *self = new RoarOutput();
-
- if (!self->Initialize(param, error)) {
- delete self;
- return nullptr;
- }
-
- self->Configure(param);
- return *self;
-}
-
-static void
-roar_finish(struct audio_output *ao)
-{
- RoarOutput *self = (RoarOutput *)ao;
-
- self->Deinitialize();
- 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;
- }
-}
-
-inline bool
-RoarOutput::Open(AudioFormat &audio_format, Error &error)
-{
- const ScopeLock protect(mutex);
-
- if (roar_simple_connect(&con,
- host.empty() ? nullptr : host.c_str(),
- name.c_str()) < 0) {
- error.Set(roar_output_domain,
- "Failed to connect to Roar server");
- return false;
- }
-
- vss = roar_vs_new_from_con(&con, &err);
-
- if (vss == nullptr || err != ROAR_ERROR_NONE) {
- error.Set(roar_output_domain, "Failed to connect to server");
- return false;
- }
-
- roar_use_audio_format(&info, audio_format);
-
- if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) {
- error.Set(roar_output_domain, "Failed to start stream");
- return false;
- }
-
- roar_vs_role(vss, role, &err);
- alive = true;
- return true;
-}
-
-static bool
-roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
-{
- RoarOutput *self = (RoarOutput *)ao;
-
- return self->Open(audio_format, error);
-}
-
-inline void
-RoarOutput::Close()
-{
- const ScopeLock protect(mutex);
-
- alive = false;
-
- if (vss != nullptr)
- roar_vs_close(vss, ROAR_VS_TRUE, &err);
- vss = nullptr;
- roar_disconnect(&con);
-}
-
-static void
-roar_close(struct audio_output *ao)
-{
- RoarOutput *self = (RoarOutput *)ao;
- self->Close();
-}
-
-inline void
-RoarOutput::Cancel()
-{
- const ScopeLock protect(mutex);
-
- if (vss == nullptr)
- return;
-
- roar_vs_t *_vss = vss;
- vss = nullptr;
- roar_vs_close(_vss, ROAR_VS_TRUE, &err);
- alive = false;
-
- _vss = roar_vs_new_from_con(&con, &err);
- if (_vss == nullptr)
- return;
-
- if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
- roar_vs_close(_vss, ROAR_VS_TRUE, &err);
- LogError(roar_output_domain, "Failed to start stream");
- return;
- }
-
- roar_vs_role(_vss, role, &err);
- vss = _vss;
- alive = true;
-}
-
-static void
-roar_cancel(struct audio_output *ao)
-{
- RoarOutput *self = (RoarOutput *)ao;
-
- self->Cancel();
-}
-
-inline size_t
-RoarOutput::Play(const void *chunk, size_t size, Error &error)
-{
- if (vss == nullptr) {
- error.Set(roar_output_domain, "Connection is invalid");
- return 0;
- }
-
- ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
- if (nbytes <= 0) {
- error.Set(roar_output_domain, "Failed to play data");
- return 0;
- }
-
- return nbytes;
-}
-
-static size_t
-roar_play(struct audio_output *ao, const void *chunk, size_t size,
- Error &error)
-{
- RoarOutput *self = (RoarOutput *)ao;
- return self->Play(chunk, size, error);
-}
-
-static const char*
-roar_tag_convert(TagType 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;
- }
-}
-
-inline void
-RoarOutput::SendTag(const Tag &tag)
-{
- if (vss == nullptr)
- return;
-
- const ScopeLock protect(mutex);
-
- size_t cnt = 1;
- struct roar_keyval vals[32];
- char uuid_buf[32][64];
-
- char timebuf[16];
- snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
- tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60);
-
- vals[0].key = const_cast<char *>("LENGTH");
- vals[0].value = timebuf;
-
- for (unsigned i = 0; i < tag.num_items && cnt < 32; i++)
- {
- bool is_uuid = false;
- const char *key = roar_tag_convert(tag.items[i]->type,
- &is_uuid);
- if (key != nullptr) {
- vals[cnt].key = const_cast<char *>(key);
-
- if (is_uuid) {
- snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
- tag.items[i]->value);
- vals[cnt].value = uuid_buf[cnt];
- } else {
- vals[cnt].value = tag.items[i]->value;
- }
-
- cnt++;
- }
- }
-
- roar_vs_meta(vss, vals, cnt, &(err));
-}
-
-static void
-roar_send_tag(struct audio_output *ao, const Tag *meta)
-{
- RoarOutput *self = (RoarOutput *)ao;
- self->SendTag(*meta);
-}
-
-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
deleted file mode 100644
index 04949e421..000000000
--- a/src/output/RoarOutputPlugin.hxx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ROAR_OUTPUT_PLUGIN_H
-#define MPD_ROAR_OUTPUT_PLUGIN_H
-
-class 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
deleted file mode 100644
index abef8d0b7..000000000
--- a/src/output/ShoutOutputPlugin.cxx
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ShoutOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "EncoderPlugin.hxx"
-#include "EncoderList.hxx"
-#include "ConfigError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "system/FatalError.hxx"
-#include "Log.hxx"
-
-#include <shout/shout.h>
-#include <glib.h>
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-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, Error &error) {
- return ao_base_init(&base, &shout_output_plugin, param,
- error);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-
- bool Configure(const config_param &param, Error &error);
-};
-
-static int shout_init_count;
-
-static constexpr Domain shout_output_domain("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)
- FormatFatalError("no \"%s\" defined for shout device defined "
- "at line %u\n", name, param.line);
-
- return value;
-}
-
-inline bool
-ShoutOutput::Configure(const config_param &param, Error &error)
-{
-
- const AudioFormat audio_format = base.config_audio_format;
- if (!audio_format.IsFullyDefined()) {
- error.Set(config_domain,
- "Need full audio format specification");
- return false;
- }
-
- 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) {
- error.Set(config_domain, "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) {
- error.Format(config_domain,
- "shout quality \"%s\" is not a number in the "
- "range -1 to 10",
- value);
- return false;
- }
-
- if (param.GetBlockValue("bitrate") != nullptr) {
- error.Set(config_domain,
- "quality and bitrate are "
- "both defined");
- return false;
- }
- } else {
- value = param.GetBlockValue("bitrate");
- if (value == nullptr) {
- error.Set(config_domain,
- "neither bitrate nor quality defined");
- return false;
- }
-
- char *test;
- bitrate = strtol(value, &test, 10);
-
- if (*test != '\0' || bitrate <= 0) {
- error.Set(config_domain,
- "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) {
- error.Format(config_domain,
- "couldn't find shout encoder plugin \"%s\"",
- encoding);
- return false;
- }
-
- encoder = encoder_init(*encoder_plugin, param, error);
- 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")) {
- error.Format(config_domain,
- "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 {
- error.Format(config_domain,
- "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) {
- error.Set(shout_output_domain, 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)) {
- error.Set(shout_output_domain, shout_get_error(shout_conn));
- return false;
- }
-
- value = param.GetBlockValue("description");
- if (value != nullptr && shout_set_description(shout_conn, value)) {
- error.Set(shout_output_domain, shout_get_error(shout_conn));
- return false;
- }
-
- value = param.GetBlockValue("url");
- if (value != nullptr && shout_set_url(shout_conn, value)) {
- error.Set(shout_output_domain, 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, Error &error)
-{
- ShoutOutput *sd = new ShoutOutput();
- if (!sd->Initialize(param, error)) {
- delete sd;
- return nullptr;
- }
-
- if (!sd->Configure(param, error)) {
- 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, Error &error)
-{
- switch (err) {
- case SHOUTERR_SUCCESS:
- break;
-
- case SHOUTERR_UNCONNECTED:
- case SHOUTERR_SOCKET:
- error.Format(shout_output_domain, 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:
- error.Format(shout_output_domain, 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, Error &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, IgnoreError()))
- write_page(sd, IgnoreError());
-
- encoder_close(sd->encoder);
- }
-
- if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
- shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
- FormatWarning(shout_output_domain,
- "problem closing connection to shout server: %s",
- 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)
-{
- gcc_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, Error &error)
-{
- switch (shout_open(sd->shout_conn)) {
- case SHOUTERR_SUCCESS:
- case SHOUTERR_CONNECTED:
- return true;
-
- default:
- error.Format(shout_output_domain,
- "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,
- Error &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,
- Error &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), IgnoreError());
-}
-
-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;
-
- if (sd->encoder->plugin.tag != nullptr) {
- /* encoder plugin supports stream tags */
-
- Error error;
- if (!encoder_pre_tag(sd->encoder, error) ||
- !write_page(sd, error) ||
- !encoder_tag(sd->encoder, tag, error)) {
- LogError(error);
- return;
- }
- } 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)) {
- LogWarning(shout_output_domain,
- "error setting shout metadata");
- }
- }
-
- write_page(sd, IgnoreError());
-}
-
-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
deleted file mode 100644
index 496b77975..000000000
--- a/src/output/ShoutOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 0836dc2e2..000000000
--- a/src/output/SolarisOutputPlugin.cxx
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SolarisOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "system/fd_util.h"
-#include "util/Error.hxx"
-
-#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
-
-struct SolarisOutput {
- struct audio_output base;
-
- /* configuration */
- const char *device;
-
- int fd;
-
- bool Initialize(const config_param &param, Error &error_r) {
- return ao_base_init(&base, &solaris_output_plugin, param,
- error_r);
- }
-
- void Deinitialize() {
- ao_base_finish(&base);
- }
-};
-
-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, Error &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,
- Error &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) {
- error.FormatErrno("Failed to open %s",
- so->device);
- 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) {
- error.SetErrno("AUDIO_GETINFO failed");
- 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) {
- error.SetErrno("AUDIO_SETINFO failed");
- 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,
- Error &error)
-{
- SolarisOutput *so = (SolarisOutput *)ao;
- ssize_t nbytes;
-
- nbytes = write(so->fd, chunk, size);
- if (nbytes <= 0) {
- error.SetErrno("Write failed");
- 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
deleted file mode 100644
index d0fbd32c8..000000000
--- a/src/output/SolarisOutputPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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/Timer.cxx b/src/output/Timer.cxx
new file mode 100644
index 000000000..d3dcc714d
--- /dev/null
+++ b/src/output/Timer.cxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Timer.hxx"
+#include "AudioFormat.hxx"
+#include "system/Clock.hxx"
+
+#include <limits>
+
+#include <assert.h>
+
+Timer::Timer(const AudioFormat af)
+ : time(0),
+ started(false),
+ rate(af.sample_rate * af.GetFrameSize())
+{
+}
+
+void Timer::Start()
+{
+ time = MonotonicClockUS();
+ 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 - MonotonicClockUS()) / 1000;
+ if (delay < 0)
+ return 0;
+
+ if (delay > std::numeric_limits<int>::max())
+ delay = std::numeric_limits<int>::max();
+
+ return delay;
+}
diff --git a/src/output/Timer.hxx b/src/output/Timer.hxx
new file mode 100644
index 000000000..3c935cfac
--- /dev/null
+++ b/src/output/Timer.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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;
+};
+
+#endif
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
deleted file mode 100644
index d2508ee2a..000000000
--- a/src/output/WinmmOutputPlugin.cxx
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "WinmmOutputPlugin.hxx"
-#include "OutputAPI.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "MixerList.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "util/Macros.hxx"
-
-#include <glib.h>
-
-#include <stdlib.h>
-#include <string.h>
-
-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;
-};
-
-static constexpr Domain winmm_output_domain("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, Error &error)
-{
- /* 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:
- error.Format(winmm_output_domain,
- "device \"%s\" is not found", device_name);
- return false;
-}
-
-static struct audio_output *
-winmm_output_init(const config_param &param, Error &error)
-{
- WinmmOutput *wo = new WinmmOutput();
- if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) {
- delete wo;
- return nullptr;
- }
-
- const char *device = param.GetBlockValue("device");
- if (!get_device_id(device, &wo->device_id, error)) {
- ao_base_finish(&wo->base);
- delete 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,
- Error &error)
-{
- WinmmOutput *wo = (WinmmOutput *)ao;
-
- wo->event = CreateEvent(nullptr, false, false, nullptr);
- if (wo->event == nullptr) {
- error.Set(winmm_output_domain, "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);
- error.Set(winmm_output_domain, "waveOutOpen() failed");
- return false;
- }
-
- for (unsigned i = 0; i < ARRAY_SIZE(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 < ARRAY_SIZE(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,
- Error &error)
-{
- 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) {
- error.Set(winmm_output_domain, result,
- "waveOutPrepareHeader() failed");
- return false;
- }
-
- return true;
-}
-
-/**
- * Wait until the buffer is finished.
- */
-static bool
-winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
- Error &error)
-{
- 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) {
- error.Set(winmm_output_domain, 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, Error &error)
-{
- 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) ||
- !winmm_set_buffer(wo, buffer, chunk, size, error))
- 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));
- error.Set(winmm_output_domain, result,
- "waveOutWrite() failed");
- return 0;
- }
-
- /* mark our buffer as "used" */
- wo->next_buffer = (wo->next_buffer + 1) %
- ARRAY_SIZE(wo->buffers);
-
- return size;
-}
-
-static bool
-winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
-{
- for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
- return false;
-
- for (unsigned i = 0; i < wo->next_buffer; ++i)
- if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
- return false;
-
- return true;
-}
-
-static void
-winmm_stop(WinmmOutput *wo)
-{
- waveOutReset(wo->handle);
-
- for (unsigned i = 0; i < ARRAY_SIZE(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, IgnoreError()))
- 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
deleted file mode 100644
index a6b7733ec..000000000
--- a/src/output/WinmmOutputPlugin.hxx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX
-#define MPD_WINMM_OUTPUT_PLUGIN_HXX
-
-#include "check.h"
-
-#ifdef ENABLE_WINMM_OUTPUT
-
-#include "Compiler.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/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
new file mode 100644
index 000000000..28c374a00
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -0,0 +1,895 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AlsaOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "pcm/PcmExport.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Manual.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/ConstBuffer.hxx"
+#include "Log.hxx"
+
+#include <alsa/asoundlib.h>
+
+#include <string>
+
+#if SND_LIB_VERSION >= 0x1001c
+/* alsa-lib supports DSD since version 1.0.27.1 */
+#define HAVE_ALSA_DSD
+#endif
+
+static const char default_device[] = "default";
+
+static constexpr unsigned MPD_ALSA_BUFFER_TIME_US = 500000;
+
+static constexpr unsigned 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 {
+ AudioOutput 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 PCM according to the DoP standard standard?
+ *
+ * @see http://dsd-guide.com/dop-open-standard
+ */
+ bool dop;
+
+ /** 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;
+
+ /**
+ * Do we need to call snd_pcm_prepare() before the next write?
+ * It means that we put the device to SND_PCM_STATE_SETUP by
+ * calling snd_pcm_drop().
+ *
+ * Without this flag, we could easily recover after a failed
+ * optimistic write (returning -EBADFD), but the Raspberry Pi
+ * audio driver is infamous for generating ugly artefacts from
+ * this.
+ */
+ bool must_prepare;
+
+ /**
+ * This buffer gets allocated after opening the ALSA device.
+ * It contains silence samples, enough to fill one period (see
+ * #period_frames).
+ */
+ uint8_t *silence;
+
+ AlsaOutput()
+ :base(alsa_output_plugin),
+ mode(0), writei(snd_pcm_writei) {
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain alsa_output_domain("alsa_output");
+
+static const char *
+alsa_device(const AlsaOutput *ad)
+{
+ return ad->device.empty() ? default_device : ad->device.c_str();
+}
+
+inline bool
+AlsaOutput::Configure(const config_param &param, Error &error)
+{
+ if (!base.Configure(param, error))
+ return false;
+
+ device = param.GetBlockValue("device", "");
+
+ use_mmap = param.GetBlockValue("use_mmap", false);
+
+ dop = param.GetBlockValue("dop", false) ||
+ /* legacy name from MPD 0.18 and older: */
+ param.GetBlockValue("dsd_usb", false);
+
+ buffer_time = param.GetBlockValue("buffer_time",
+ MPD_ALSA_BUFFER_TIME_US);
+ period_time = param.GetBlockValue("period_time", 0u);
+
+#ifdef SND_PCM_NO_AUTO_RESAMPLE
+ if (!param.GetBlockValue("auto_resample", true))
+ mode |= SND_PCM_NO_AUTO_RESAMPLE;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_CHANNELS
+ if (!param.GetBlockValue("auto_channels", true))
+ mode |= SND_PCM_NO_AUTO_CHANNELS;
+#endif
+
+#ifdef SND_PCM_NO_AUTO_FORMAT
+ if (!param.GetBlockValue("auto_format", true))
+ mode |= SND_PCM_NO_AUTO_FORMAT;
+#endif
+
+ return true;
+}
+
+static AudioOutput *
+alsa_init(const config_param &param, Error &error)
+{
+ AlsaOutput *ad = new AlsaOutput();
+
+ if (!ad->Configure(param, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ return &ad->base;
+}
+
+static void
+alsa_finish(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ delete ad;
+
+ /* free libasound's config cache */
+ snd_config_update_free_global();
+}
+
+static bool
+alsa_output_enable(AudioOutput *ao, gcc_unused Error &error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->pcm_export.Construct();
+ return true;
+}
+
+static void
+alsa_output_disable(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->pcm_export.Destruct();
+}
+
+static bool
+alsa_test_default_device()
+{
+ snd_pcm_t *handle;
+
+ int ret = snd_pcm_open(&handle, default_device,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (ret) {
+ FormatError(alsa_output_domain,
+ "Error opening default ALSA device: %s",
+ snd_strerror(-ret));
+ return false;
+ } else
+ snd_pcm_close(handle);
+
+ return true;
+}
+
+/**
+ * Convert MPD's #SampleFormat enum to libasound's snd_pcm_format_t
+ * enum. Returns SND_PCM_FORMAT_UNKNOWN if there is no according ALSA
+ * PCM format.
+ */
+static snd_pcm_format_t
+get_bitformat(SampleFormat sample_format)
+{
+ switch (sample_format) {
+ case SampleFormat::UNDEFINED:
+ return SND_PCM_FORMAT_UNKNOWN;
+
+ case SampleFormat::DSD:
+#ifdef HAVE_ALSA_DSD
+ return SND_PCM_FORMAT_DSD_U8;
+#else
+ return SND_PCM_FORMAT_UNKNOWN;
+#endif
+
+ 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();
+}
+
+/**
+ * Determine the byte-swapped PCM format. Returns
+ * SND_PCM_FORMAT_UNKNOWN if the format cannot be byte-swapped.
+ */
+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;
+ }
+}
+
+/**
+ * Check if there is a "packed" version of the give PCM format.
+ * Returns SND_PCM_FORMAT_UNKNOWN if not.
+ */
+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;
+ }
+}
+
+/**
+ * Attempts to configure the specified sample format. On failure,
+ * fall back to the packed version.
+ */
+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 constexpr 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, Error &error)
+{
+ unsigned int sample_rate = audio_format.sample_rate;
+ unsigned int channels = audio_format.channels;
+ int err;
+ const char *cmd = nullptr;
+ unsigned 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) {
+ FormatWarning(alsa_output_domain,
+ "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
+ alsa_device(ad), snd_strerror(-err));
+ LogWarning(alsa_output_domain,
+ "Falling back to direct write mode");
+ 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) {
+ error.Format(alsa_output_domain, 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)
+ FormatDebug(alsa_output_domain,
+ "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) {
+ error.Format(alsa_output_domain, 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, nullptr);
+ if (err < 0 || sample_rate == 0) {
+ error.Format(alsa_output_domain, 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);
+ FormatDebug(alsa_output_domain, "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);
+ FormatDebug(alsa_output_domain, "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, nullptr);
+ if (err < 0)
+ goto error;
+ } else {
+ err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time,
+ nullptr);
+ if (err < 0)
+ buffer_time = 0;
+ }
+
+ if (period_time_ro == 0 && buffer_time >= 10000) {
+ period_time_ro = period_time = buffer_time / 4;
+
+ FormatDebug(alsa_output_domain,
+ "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, nullptr);
+ 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)
+ FormatDebug(alsa_output_domain,
+ "ALSA period_time set to %d", 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,
+ nullptr);
+ 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;
+
+ FormatDebug(alsa_output_domain, "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 = new uint8_t[snd_pcm_frames_to_bytes(ad->pcm,
+ alsa_period_size)];
+ snd_pcm_format_set_silence(format, ad->silence,
+ alsa_period_size * channels);
+
+ return true;
+
+error:
+ error.Format(alsa_output_domain, err,
+ "Error opening ALSA device \"%s\" (%s): %s",
+ alsa_device(ad), cmd, snd_strerror(-err));
+ return false;
+}
+
+static bool
+alsa_setup_dop(AlsaOutput *ad, const AudioFormat audio_format,
+ bool *shift8_r, bool *packed_r, bool *reverse_endian_r,
+ Error &error)
+{
+ assert(ad->dop);
+ assert(audio_format.format == SampleFormat::DSD);
+
+ /* pass 24 bit to alsa_setup() */
+
+ AudioFormat dop_format = audio_format;
+ dop_format.format = SampleFormat::S24_P32;
+ dop_format.sample_rate /= 2;
+
+ const AudioFormat check = dop_format;
+
+ if (!alsa_setup(ad, dop_format, packed_r, reverse_endian_r, error))
+ return false;
+
+ /* if the device allows only 32 bit, shift all DoP
+ 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 = dop_format.format == SampleFormat::S32;
+ if (dop_format.format == SampleFormat::S32)
+ dop_format.format = SampleFormat::S24_P32;
+
+ if (dop_format != check) {
+ /* no bit-perfect playback, which is required
+ for DSD over USB */
+ error.Format(alsa_output_domain,
+ "Failed to configure DSD-over-PCM on ALSA device \"%s\"",
+ alsa_device(ad));
+ delete[] ad->silence;
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+alsa_setup_or_dop(AlsaOutput *ad, AudioFormat &audio_format,
+ Error &error)
+{
+ bool shift8 = false, packed, reverse_endian;
+
+ const bool dop = ad->dop &&
+ audio_format.format == SampleFormat::DSD;
+ const bool success = dop
+ ? alsa_setup_dop(ad, audio_format,
+ &shift8, &packed, &reverse_endian,
+ error)
+ : alsa_setup(ad, audio_format, &packed, &reverse_endian,
+ error);
+ if (!success)
+ return false;
+
+ ad->pcm_export->Open(audio_format.format,
+ audio_format.channels,
+ dop, shift8, packed, reverse_endian);
+ return true;
+}
+
+static bool
+alsa_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
+ SND_PCM_STREAM_PLAYBACK, ad->mode);
+ if (err < 0) {
+ error.Format(alsa_output_domain, err,
+ "Failed to open ALSA device \"%s\": %s",
+ alsa_device(ad), snd_strerror(err));
+ return false;
+ }
+
+ FormatDebug(alsa_output_domain, "opened %s type=%s",
+ snd_pcm_name(ad->pcm),
+ snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+
+ if (!alsa_setup_or_dop(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);
+
+ ad->must_prepare = false;
+
+ 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) {
+ FormatDebug(alsa_output_domain,
+ "Underrun on ALSA device \"%s\"", alsa_device(ad));
+ } else if (err == -ESTRPIPE) {
+ FormatDebug(alsa_output_domain,
+ "ALSA device \"%s\" was suspended",
+ 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(AudioOutput *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(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ ad->period_position = 0;
+ ad->must_prepare = true;
+
+ snd_pcm_drop(ad->pcm);
+}
+
+static void
+alsa_close(AudioOutput *ao)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ snd_pcm_close(ad->pcm);
+ delete[] ad->silence;
+}
+
+static size_t
+alsa_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ AlsaOutput *ad = (AlsaOutput *)ao;
+
+ assert(size > 0);
+ assert(size % ad->in_frame_size == 0);
+
+ if (ad->must_prepare) {
+ ad->must_prepare = false;
+
+ int err = snd_pcm_prepare(ad->pcm);
+ if (err < 0) {
+ error.Set(alsa_output_domain, err, snd_strerror(-err));
+ return 0;
+ }
+ }
+
+ const auto e = ad->pcm_export->Export({chunk, size});
+ if (e.size == 0)
+ /* the DoP (DSD over PCM) filter converts two frames
+ at a time and ignores the last odd frame; if there
+ was only one frame (e.g. the last frame in the
+ file), the result is empty; to avoid an endless
+ loop, bail out here, and pretend the one frame has
+ been played */
+ return size;
+
+ chunk = e.data;
+ size = e.size;
+
+ assert(size % ad->out_frame_size == 0);
+
+ size /= ad->out_frame_size;
+ assert(size > 0);
+
+ 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) {
+ error.Set(alsa_output_domain, ret, snd_strerror(-ret));
+ return 0;
+ }
+ }
+}
+
+const struct AudioOutputPlugin 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/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx
new file mode 100644
index 000000000..f72116f91
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ALSA_OUTPUT_PLUGIN_HXX
+#define MPD_ALSA_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin alsa_output_plugin;
+
+#endif
diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx
new file mode 100644
index 000000000..af8c88fa1
--- /dev/null
+++ b/src/output/plugins/AoOutputPlugin.cxx
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AoOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <ao/ao.h>
+#include <glib.h>
+
+#include <string.h>
+
+/* 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 {
+ AudioOutput base;
+
+ size_t write_size;
+ int driver;
+ ao_option *options;
+ ao_device *device;
+
+ AoOutput()
+ :base(ao_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain ao_output_domain("ao_output");
+
+static void
+ao_output_error(Error &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_r.SetErrno();
+ return;
+ }
+
+ error_r.Set(ao_output_domain, errno, error);
+}
+
+inline bool
+AoOutput::Configure(const config_param &param, Error &error)
+{
+ 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) {
+ error.Format(ao_output_domain,
+ "\"%s\" is not a valid ao driver",
+ value);
+ return false;
+ }
+
+ ao_info *ai = ao_driver_info(driver);
+ if (ai == nullptr) {
+ error.Set(ao_output_domain, "problems getting driver info");
+ return false;
+ }
+
+ FormatDebug(ao_output_domain, "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) {
+ error.Format(ao_output_domain,
+ "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 AudioOutput *
+ao_output_init(const config_param &param, Error &error)
+{
+ AoOutput *ad = new AoOutput();
+
+ if (!ad->Initialize(param, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ if (!ad->Configure(param, error)) {
+ delete ad;
+ return nullptr;
+ }
+
+ return &ad->base;
+}
+
+static void
+ao_output_finish(AudioOutput *ao)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ ao_free_options(ad->options);
+ delete ad;
+
+ ao_output_ref--;
+
+ if (ao_output_ref == 0)
+ ao_shutdown();
+}
+
+static void
+ao_output_close(AudioOutput *ao)
+{
+ AoOutput *ad = (AoOutput *)ao;
+
+ ao_close(ad->device);
+}
+
+static bool
+ao_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &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(AudioOutput *ao, const void *chunk, size_t size,
+ Error &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 AudioOutputPlugin 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/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx
new file mode 100644
index 000000000..07c2ba16b
--- /dev/null
+++ b/src/output/plugins/AoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AO_OUTPUT_PLUGIN_HXX
+#define MPD_AO_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin ao_output_plugin;
+
+#endif
diff --git a/src/output/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx
new file mode 100644
index 000000000..9df5a74dd
--- /dev/null
+++ b/src/output/plugins/FifoOutputPlugin.cxx
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FifoOutputPlugin.hxx"
+#include "config/ConfigError.hxx"
+#include "../OutputAPI.hxx"
+#include "../Timer.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+#include "open.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
+
+struct FifoOutput {
+ AudioOutput base;
+
+ AllocatedPath path;
+ std::string path_utf8;
+
+ int input;
+ int output;
+ bool created;
+ Timer *timer;
+
+ FifoOutput()
+ :base(fifo_output_plugin),
+ path(AllocatedPath::Null()), input(-1), output(-1),
+ created(false) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Create(Error &error);
+ bool Check(Error &error);
+ void Delete();
+
+ bool Open(Error &error);
+ void Close();
+};
+
+static constexpr Domain fifo_output_domain("fifo_output");
+
+inline void
+FifoOutput::Delete()
+{
+ FormatDebug(fifo_output_domain,
+ "Removing FIFO \"%s\"", path_utf8.c_str());
+
+ if (!RemoveFile(path)) {
+ FormatErrno(fifo_output_domain,
+ "Could not remove FIFO \"%s\"",
+ path_utf8.c_str());
+ return;
+ }
+
+ created = false;
+}
+
+void
+FifoOutput::Close()
+{
+ if (input >= 0) {
+ close(input);
+ input = -1;
+ }
+
+ if (output >= 0) {
+ close(output);
+ output = -1;
+ }
+
+ struct stat st;
+ if (created && StatFile(path, st))
+ Delete();
+}
+
+inline bool
+FifoOutput::Create(Error &error)
+{
+ if (!MakeFifo(path, 0666)) {
+ error.FormatErrno("Couldn't create FIFO \"%s\"",
+ path_utf8.c_str());
+ return false;
+ }
+
+ created = true;
+ return true;
+}
+
+inline bool
+FifoOutput::Check(Error &error)
+{
+ struct stat st;
+ if (!StatFile(path, st)) {
+ if (errno == ENOENT) {
+ /* Path doesn't exist */
+ return Create(error);
+ }
+
+ error.FormatErrno("Failed to stat FIFO \"%s\"",
+ path_utf8.c_str());
+ return false;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ error.Format(fifo_output_domain,
+ "\"%s\" already exists, but is not a FIFO",
+ path_utf8.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+inline bool
+FifoOutput::Open(Error &error)
+{
+ if (!Check(error))
+ return false;
+
+ input = OpenFile(path, O_RDONLY|O_NONBLOCK|O_BINARY, 0);
+ if (input < 0) {
+ error.FormatErrno("Could not open FIFO \"%s\" for reading",
+ path_utf8.c_str());
+ Close();
+ return false;
+ }
+
+ output = OpenFile(path, O_WRONLY|O_NONBLOCK|O_BINARY, 0);
+ if (output < 0) {
+ error.FormatErrno("Could not open FIFO \"%s\" for writing",
+ path_utf8.c_str());
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+fifo_open(FifoOutput *fd, Error &error)
+{
+ return fd->Open(error);
+}
+
+static AudioOutput *
+fifo_output_init(const config_param &param, Error &error)
+{
+ FifoOutput *fd = new FifoOutput();
+
+ fd->path = param.GetBlockPath("path", error);
+ if (fd->path.IsNull()) {
+ delete fd;
+
+ if (!error.IsDefined())
+ error.Set(config_domain,
+ "No \"path\" parameter specified");
+ return nullptr;
+ }
+
+ fd->path_utf8 = fd->path.ToUTF8();
+
+ if (!fd->Initialize(param, error)) {
+ delete fd;
+ return nullptr;
+ }
+
+ if (!fifo_open(fd, error)) {
+ delete fd;
+ return nullptr;
+ }
+
+ return &fd->base;
+}
+
+static void
+fifo_output_finish(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ fd->Close();
+ delete fd;
+}
+
+static bool
+fifo_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ gcc_unused Error &error)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ fd->timer = new Timer(audio_format);
+
+ return true;
+}
+
+static void
+fifo_output_close(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ delete fd->timer;
+}
+
+static void
+fifo_output_cancel(AudioOutput *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) {
+ FormatErrno(fifo_output_domain,
+ "Flush of FIFO \"%s\" failed",
+ fd->path_utf8.c_str());
+ }
+}
+
+static unsigned
+fifo_output_delay(AudioOutput *ao)
+{
+ FifoOutput *fd = (FifoOutput *)ao;
+
+ return fd->timer->IsStarted()
+ ? fd->timer->GetDelay()
+ : 0;
+}
+
+static size_t
+fifo_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &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;
+ }
+
+ error.FormatErrno("Failed to write to FIFO %s",
+ fd->path_utf8.c_str());
+ return 0;
+ }
+ }
+}
+
+const struct AudioOutputPlugin 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/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx
new file mode 100644
index 000000000..f41ceded6
--- /dev/null
+++ b/src/output/plugins/FifoOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FIFO_OUTPUT_PLUGIN_HXX
+#define MPD_FIFO_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin fifo_output_plugin;
+
+#endif
diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx
new file mode 100644
index 000000000..e1dad7893
--- /dev/null
+++ b/src/output/plugins/JackOutputPlugin.cxx
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "JackOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+enum {
+ MAX_PORTS = 16,
+};
+
+static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t);
+
+struct JackOutput {
+ AudioOutput 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;
+
+ JackOutput()
+ :base(jack_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static constexpr Domain jack_output_domain("jack_output");
+
+/**
+ * Determine the number of frames guaranteed to be available on all
+ * channels.
+ */
+static jack_nframes_t
+mpd_jack_available(const JackOutput *jd)
+{
+ 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)
+{
+ LogError(jack_output_domain, msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+ LogDefault(jack_output_domain, 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, Error &error)
+{
+ 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) {
+ error.Format(jack_output_domain, status,
+ "Failed to connect to JACK server, status=%d",
+ status);
+ return false;
+ }
+
+ jack_set_process_callback(jd->client, mpd_jack_process, jd);
+ jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+ 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) {
+ error.Format(jack_output_domain,
+ "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(const char *source, char **dest, Error &error)
+{
+ char **list = g_strsplit(source, ",", 0);
+ unsigned n = 0;
+
+ for (n = 0; list[n] != nullptr; ++n) {
+ if (n >= MAX_PORTS) {
+ error.Set(config_domain,
+ "too many port names");
+ return 0;
+ }
+
+ dest[n] = list[n];
+ }
+
+ g_free(list);
+
+ if (n == 0) {
+ error.Format(config_domain,
+ "at least one port name expected");
+ return 0;
+ }
+
+ return n;
+}
+
+static AudioOutput *
+mpd_jack_init(const config_param &param, Error &error)
+{
+ JackOutput *jd = new JackOutput();
+
+ if (!jd->Initialize(param, error)) {
+ 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(value,
+ jd->source_ports, error);
+ 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)
+ FormatWarning(jack_output_domain,
+ "deprecated option 'ports' in line %d",
+ param.line);
+ }
+
+ if (value != nullptr) {
+ jd->num_destination_ports =
+ parse_port_list(value,
+ jd->destination_ports, error);
+ 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)
+ FormatWarning(jack_output_domain,
+ "number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ 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(AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ g_free(jd->source_ports[i]);
+
+ for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+ g_free(jd->destination_ports[i]);
+
+ delete jd;
+}
+
+static bool
+mpd_jack_enable(AudioOutput *ao, Error &error)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ jd->ringbuffer[i] = nullptr;
+
+ return mpd_jack_connect(jd, error);
+}
+
+static void
+mpd_jack_disable(AudioOutput *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, Error &error)
+{
+ const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = nullptr;
+ unsigned num_destination_ports;
+
+ assert(jd->client != nullptr);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
+
+ /* 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) ) {
+ error.Set(jack_output_domain, "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) {
+ error.Set(jack_output_domain, "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) {
+ FormatDebug(jack_output_domain,
+ "destination_port[%u] = '%s'\n",
+ num_destination_ports,
+ jports[num_destination_ports]);
+ destination_ports[num_destination_ports] =
+ jports[num_destination_ports];
+ }
+ } 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) {
+ error.Format(jack_output_domain,
+ "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) {
+ error.Format(jack_output_domain,
+ "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(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ 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))
+ return false;
+
+ set_audioformat(jd, audio_format);
+ jd->audio_format = audio_format;
+
+ if (!mpd_jack_start(jd, error))
+ return false;
+
+ return true;
+}
+
+static void
+mpd_jack_close(gcc_unused AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ mpd_jack_stop(jd);
+}
+
+static unsigned
+mpd_jack_delay(AudioOutput *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(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ JackOutput *jd = (JackOutput *)ao;
+ const size_t frame_size = jd->audio_format.GetFrameSize();
+ size_t space = 0, space1;
+
+ jd->pause = false;
+
+ assert(size % frame_size == 0);
+ size /= frame_size;
+
+ while (true) {
+ if (jd->shutdown) {
+ error.Set(jack_output_domain,
+ "Refusing to play, because "
+ "there is no client thread");
+ return 0;
+ }
+
+ space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
+
+ if (space >= jack_sample_size)
+ break;
+
+ /* 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(AudioOutput *ao)
+{
+ JackOutput *jd = (JackOutput *)ao;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ return true;
+}
+
+const struct AudioOutputPlugin 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/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx
new file mode 100644
index 000000000..6f1f7ecb9
--- /dev/null
+++ b/src/output/plugins/JackOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX
+#define MPD_JACK_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin jack_output_plugin;
+
+#endif
diff --git a/src/output/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx
new file mode 100644
index 000000000..098f58926
--- /dev/null
+++ b/src/output/plugins/NullOutputPlugin.cxx
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NullOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "../Timer.hxx"
+
+struct NullOutput {
+ AudioOutput base;
+
+ bool sync;
+
+ Timer *timer;
+
+ NullOutput()
+ :base(null_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+};
+
+static AudioOutput *
+null_init(const config_param &param, Error &error)
+{
+ NullOutput *nd = new NullOutput();
+
+ if (!nd->Initialize(param, error)) {
+ delete nd;
+ return nullptr;
+ }
+
+ nd->sync = param.GetBlockValue("sync", true);
+
+ return &nd->base;
+}
+
+static void
+null_finish(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ delete nd;
+}
+
+static bool
+null_open(AudioOutput *ao, AudioFormat &audio_format,
+ gcc_unused Error &error)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (nd->sync)
+ nd->timer = new Timer(audio_format);
+
+ return true;
+}
+
+static void
+null_close(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (nd->sync)
+ delete nd->timer;
+}
+
+static unsigned
+null_delay(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ return nd->sync && nd->timer->IsStarted()
+ ? nd->timer->GetDelay()
+ : 0;
+}
+
+static size_t
+null_play(AudioOutput *ao, gcc_unused const void *chunk, size_t size,
+ gcc_unused Error &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(AudioOutput *ao)
+{
+ NullOutput *nd = (NullOutput *)ao;
+
+ if (!nd->sync)
+ return;
+
+ nd->timer->Reset();
+}
+
+const struct AudioOutputPlugin 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/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx
new file mode 100644
index 000000000..f25f5b9f3
--- /dev/null
+++ b/src/output/plugins/NullOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NULL_OUTPUT_PLUGIN_HXX
+#define MPD_NULL_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin null_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
new file mode 100644
index 000000000..13ac7b35e
--- /dev/null
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OSXOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <CoreAudio/AudioHardware.h>
+#include <AudioUnit/AudioUnit.h>
+#include <CoreServices/CoreServices.h>
+
+struct OSXOutput {
+ AudioOutput base;
+
+ /* configuration settings */
+ OSType component_subtype;
+ /* only applicable with kAudioUnitSubType_HALOutput */
+ const char *device_name;
+
+ AudioUnit au;
+ Mutex mutex;
+ Cond condition;
+
+ DynamicFifoBuffer<uint8_t> *buffer;
+
+ OSXOutput()
+ :base(osx_output_plugin) {}
+};
+
+static constexpr Domain osx_output_domain("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 strdup() this? */
+ oo->device_name = device;
+ }
+}
+
+static AudioOutput *
+osx_output_init(const config_param &param, Error &error)
+{
+ OSXOutput *oo = new OSXOutput();
+ if (!oo->base.Configure(param, error)) {
+ delete oo;
+ return NULL;
+ }
+
+ osx_output_configure(oo, param);
+
+ return &oo->base;
+}
+
+static void
+osx_output_finish(AudioOutput *ao)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ delete oo;
+}
+
+static bool
+osx_output_set_device(OSXOutput *oo, Error &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) {
+ error.Format(osx_output_domain, 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) {
+ error.Format(osx_output_domain, 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) {
+ error.Format(osx_output_domain, 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) {
+ FormatDebug(osx_output_domain,
+ "found matching device: ID=%u, name=%s",
+ (unsigned)deviceids[i], name);
+ break;
+ }
+ }
+ if (i == numdevices) {
+ FormatWarning(osx_output_domain,
+ "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) {
+ error.Format(osx_output_domain, status,
+ "Unable to set OS X audio output device: %s",
+ GetMacOSStatusCommentString(status));
+ ret = false;
+ goto done;
+ }
+
+ FormatDebug(osx_output_domain,
+ "set OS X audio output device ID=%u, name=%s",
+ (unsigned)deviceids[i], name);
+
+done:
+ delete[] deviceids;
+ return ret;
+}
+
+static OSStatus
+osx_render(void *vdata,
+ gcc_unused AudioUnitRenderActionFlags *io_action_flags,
+ gcc_unused const AudioTimeStamp *in_timestamp,
+ gcc_unused UInt32 in_bus_number,
+ gcc_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();
+
+ auto src = od->buffer->Read();
+ if (!src.IsEmpty()) {
+ if (src.size > buffer_size)
+ src.size = buffer_size;
+
+ memcpy(buffer->mData, src.data, src.size);
+ od->buffer->Consume(src.size);
+ }
+
+ od->condition.signal();
+ od->mutex.unlock();
+
+ buffer->mDataByteSize = src.size;
+
+ 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(AudioOutput *ao, Error &error)
+{
+ 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) {
+ error.Set(osx_output_domain,
+ "Error finding OS X component");
+ return false;
+ }
+
+ OSStatus status = OpenAComponent(comp, &oo->au);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to open OS X component: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ if (!osx_output_set_device(oo, error)) {
+ 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);
+ error.Set(osx_output_domain, result,
+ "unable to set callback for OS X audio unit");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+osx_output_disable(AudioOutput *ao)
+{
+ OSXOutput *oo = (OSXOutput *)ao;
+
+ CloseComponent(oo->au);
+}
+
+static void
+osx_output_cancel(AudioOutput *ao)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ const ScopeLock protect(od->mutex);
+ od->buffer->Clear();
+}
+
+static void
+osx_output_close(AudioOutput *ao)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ AudioOutputUnitStop(od->au);
+ AudioUnitUninitialize(od->au);
+
+ delete od->buffer;
+}
+
+static bool
+osx_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &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 (IsBigEndian())
+ stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+
+ 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) {
+ error.Set(osx_output_domain, result,
+ "Unable to set format on OS X device");
+ return false;
+ }
+
+ OSStatus status = AudioUnitInitialize(od->au);
+ if (status != noErr) {
+ error.Format(osx_output_domain, status,
+ "Unable to initialize OS X audio unit: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ /* create a buffer of 1s */
+ od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate *
+ audio_format.GetFrameSize());
+
+ status = AudioOutputUnitStart(od->au);
+ if (status != 0) {
+ AudioUnitUninitialize(od->au);
+ error.Format(osx_output_domain, status,
+ "unable to start audio output: %s",
+ GetMacOSStatusCommentString(status));
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+osx_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ gcc_unused Error &error)
+{
+ OSXOutput *od = (OSXOutput *)ao;
+
+ const ScopeLock protect(od->mutex);
+
+ DynamicFifoBuffer<uint8_t>::Range dest;
+ while (true) {
+ dest = od->buffer->Write();
+ if (!dest.IsEmpty())
+ break;
+
+ /* wait for some free space in the buffer */
+ od->condition.wait(od->mutex);
+ }
+
+ if (size > dest.size)
+ size = dest.size;
+
+ memcpy(dest.data, chunk, size);
+ od->buffer->Append(size);
+
+ return size;
+}
+
+const struct AudioOutputPlugin 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/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx
new file mode 100644
index 000000000..d7aed40b6
--- /dev/null
+++ b/src/output/plugins/OSXOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSX_OUTPUT_PLUGIN_HXX
+#define MPD_OSX_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin osx_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx
new file mode 100644
index 000000000..2f095c0a4
--- /dev/null
+++ b/src/output/plugins/OpenALOutputPlugin.cxx
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OpenALOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <unistd.h>
+
+#ifndef __APPLE__
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct OpenALOutput {
+ AudioOutput base;
+
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ ALuint buffers[NUM_BUFFERS];
+ unsigned filled;
+ ALuint source;
+ ALenum format;
+ ALuint frequency;
+
+ OpenALOutput()
+ :base(openal_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static constexpr Domain openal_output_domain("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);
+ }
+}
+
+gcc_pure
+static inline ALint
+openal_get_source_i(const OpenALOutput *od, ALenum param)
+{
+ ALint value;
+ alGetSourcei(od->source, param, &value);
+ return value;
+}
+
+gcc_pure
+static inline bool
+openal_has_processed(const OpenALOutput *od)
+{
+ return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
+}
+
+gcc_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, Error &error)
+{
+ od->device = alcOpenDevice(od->device_name);
+
+ if (od->device == nullptr) {
+ error.Format(openal_output_domain,
+ "Error opening OpenAL device \"%s\"",
+ od->device_name);
+ return false;
+ }
+
+ od->context = alcCreateContext(od->device, nullptr);
+
+ if (od->context == nullptr) {
+ error.Format(openal_output_domain,
+ "Error creating context for \"%s\"",
+ od->device_name);
+ alcCloseDevice(od->device);
+ return false;
+ }
+
+ return true;
+}
+
+static AudioOutput *
+openal_init(const config_param &param, Error &error)
+{
+ 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)) {
+ delete od;
+ return nullptr;
+ }
+
+ od->device_name = device_name;
+
+ return &od->base;
+}
+
+static void
+openal_finish(AudioOutput *ao)
+{
+ OpenALOutput *od = (OpenALOutput *)ao;
+
+ delete od;
+}
+
+static bool
+openal_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &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) {
+ error.Set(openal_output_domain, "Failed to generate buffers");
+ return false;
+ }
+
+ alGenSources(1, &od->source);
+
+ if (alGetError() != AL_NO_ERROR) {
+ error.Set(openal_output_domain, "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(AudioOutput *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(AudioOutput *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(AudioOutput *ao, const void *chunk, size_t size,
+ gcc_unused Error &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))
+ 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(AudioOutput *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 AudioOutputPlugin 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/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx
new file mode 100644
index 000000000..a27e6b53c
--- /dev/null
+++ b/src/output/plugins/OpenALOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OPENAL_OUTPUT_PLUGIN_HXX
+#define MPD_OPENAL_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin openal_output_plugin;
+
+#endif
diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx
new file mode 100644
index 000000000..39d87fc35
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -0,0 +1,779 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "OssOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "system/fd_util.h"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+
+#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 {
+ AudioOutput 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()
+ :base(oss_output_plugin),
+ fd(-1), device(nullptr) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+static constexpr Domain oss_output_domain("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 = ARRAY_SIZE(default_devices); --i >= 0; ) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
+ close(fd);
+ return true;
+ }
+
+ FormatErrno(oss_output_domain,
+ "Error opening OSS device \"%s\"",
+ default_devices[i]);
+ }
+
+ return false;
+}
+
+static AudioOutput *
+oss_open_default(Error &error)
+{
+ int err[ARRAY_SIZE(default_devices)];
+ enum oss_stat ret[ARRAY_SIZE(default_devices)];
+
+ const config_param empty;
+ for (int i = ARRAY_SIZE(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 = ARRAY_SIZE(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:
+ FormatWarning(oss_output_domain,
+ "%s not found", dev);
+ break;
+ case OSS_STAT_NOT_CHAR_DEV:
+ FormatWarning(oss_output_domain,
+ "%s is not a character device", dev);
+ break;
+ case OSS_STAT_NO_PERMS:
+ FormatWarning(oss_output_domain,
+ "%s: permission denied", dev);
+ break;
+ case OSS_STAT_OTHER:
+ FormatErrno(oss_output_domain, err[i],
+ "Error accessing %s", dev);
+ }
+ }
+
+ error.Set(oss_output_domain,
+ "error trying to open default OSS device");
+ return NULL;
+}
+
+static AudioOutput *
+oss_output_init(const config_param &param, Error &error)
+{
+ const char *device = param.GetBlockValue("device");
+ if (device != NULL) {
+ OssOutput *od = new OssOutput();
+ if (!od->Initialize(param, error)) {
+ delete od;
+ return NULL;
+ }
+
+ od->device = device;
+ return &od->base;
+ }
+
+ return oss_open_default(error);
+}
+
+static void
+oss_output_finish(AudioOutput *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ delete od;
+}
+
+#ifdef AFMT_S24_PACKED
+
+static bool
+oss_output_enable(AudioOutput *ao, gcc_unused Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->pcm_export.Construct();
+ return true;
+}
+
+static void
+oss_output_disable(AudioOutput *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 an #Error.
+ */
+static enum oss_setup_result
+oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
+ const char *msg, Error &error)
+{
+ assert(fd >= 0);
+ assert(value_r != NULL);
+ assert(msg != NULL);
+ assert(!error.IsDefined());
+
+ int ret = ioctl(fd, request, value_r);
+ if (ret >= 0)
+ return SUCCESS;
+
+ if (errno == EINVAL)
+ return UNSUPPORTED;
+
+ error.SetErrno(msg);
+ 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 an #Error.
+ */
+static enum oss_setup_result
+oss_try_ioctl(int fd, unsigned long request, int value,
+ const char *msg, Error &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, Error &error)
+{
+ 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);
+ 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);
+ switch (result) {
+ case SUCCESS:
+ if (!audio_valid_channel_count(channels))
+ break;
+
+ audio_format.channels = channels;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ error.Set(oss_output_domain, 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,
+ Error &error)
+{
+ 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);
+ 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);
+ 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;
+ }
+ }
+
+ error.Set(oss_output_domain, 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
+ Error &error)
+{
+ 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);
+
+#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);
+ }
+#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 &&
+ !IsLittleEndian());
+#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
+ Error &error)
+{
+ 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);
+ 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);
+ switch (result) {
+ case SUCCESS:
+ audio_format.format = mpd_format;
+ return true;
+
+ case ERROR:
+ return false;
+
+ case UNSUPPORTED:
+ break;
+ }
+ }
+
+ error.Set(oss_output_domain, "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,
+ Error &error)
+{
+ return oss_setup_channels(od->fd, audio_format, error) &&
+ oss_setup_sample_rate(od->fd, audio_format, error) &&
+ oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
+#ifdef AFMT_S24_PACKED
+ od->pcm_export,
+#endif
+ error);
+}
+
+/**
+ * Reopen the device with the saved audio_format, without any probing.
+ */
+static bool
+oss_reopen(OssOutput *od, Error &error)
+{
+ assert(od->fd < 0);
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ error.FormatErrno("Error opening OSS device \"%s\"",
+ od->device);
+ 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);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, 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);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, 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);
+ if (result != SUCCESS) {
+ oss_close(od);
+ if (result == UNSUPPORTED)
+ error.Set(oss_output_domain, msg3);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+oss_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
+ error.FormatErrno("Error opening OSS device \"%s\"",
+ od->device);
+ 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(AudioOutput *ao)
+{
+ OssOutput *od = (OssOutput *)ao;
+
+ oss_close(od);
+}
+
+static void
+oss_output_cancel(AudioOutput *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(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ OssOutput *od = (OssOutput *)ao;
+ ssize_t ret;
+
+ assert(size > 0);
+
+ /* reopen the device since it was closed by dropBufferedAudio */
+ if (od->fd < 0 && !oss_reopen(od, error))
+ return 0;
+
+#ifdef AFMT_S24_PACKED
+ const auto e = od->pcm_export->Export({chunk, size});
+ chunk = e.data;
+ size = e.size;
+#endif
+
+ assert(size > 0);
+
+ 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) {
+ error.FormatErrno("Write error on %s", od->device);
+ return 0;
+ }
+ }
+}
+
+const struct AudioOutputPlugin 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/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx
new file mode 100644
index 000000000..f9970c8f0
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OSS_OUTPUT_PLUGIN_HXX
+#define MPD_OSS_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin oss_output_plugin;
+
+#endif
diff --git a/src/output/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx
new file mode 100644
index 000000000..7a1f32258
--- /dev/null
+++ b/src/output/plugins/PipeOutputPlugin.cxx
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PipeOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <string>
+
+#include <stdio.h>
+
+struct PipeOutput {
+ AudioOutput base;
+
+ std::string cmd;
+ FILE *fh;
+
+ PipeOutput()
+ :base(pipe_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static constexpr Domain pipe_output_domain("pipe_output");
+
+inline bool
+PipeOutput::Configure(const config_param &param, Error &error)
+{
+ cmd = param.GetBlockValue("command", "");
+ if (cmd.empty()) {
+ error.Set(config_domain,
+ "No \"command\" parameter specified");
+ return false;
+ }
+
+ return true;
+}
+
+static AudioOutput *
+pipe_output_init(const config_param &param, Error &error)
+{
+ PipeOutput *pd = new PipeOutput();
+
+ if (!pd->Initialize(param, error)) {
+ delete pd;
+ return nullptr;
+ }
+
+ if (!pd->Configure(param, error)) {
+ delete pd;
+ return nullptr;
+ }
+
+ return &pd->base;
+}
+
+static void
+pipe_output_finish(AudioOutput *ao)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ delete pd;
+}
+
+static bool
+pipe_output_open(AudioOutput *ao,
+ gcc_unused AudioFormat &audio_format,
+ Error &error)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ pd->fh = popen(pd->cmd.c_str(), "w");
+ if (pd->fh == nullptr) {
+ error.FormatErrno("Error opening pipe \"%s\"",
+ pd->cmd.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+static void
+pipe_output_close(AudioOutput *ao)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+
+ pclose(pd->fh);
+}
+
+static size_t
+pipe_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ PipeOutput *pd = (PipeOutput *)ao;
+ size_t ret;
+
+ ret = fwrite(chunk, 1, size, pd->fh);
+ if (ret == 0)
+ error.SetErrno("Write error on pipe");
+
+ return ret;
+}
+
+const struct AudioOutputPlugin 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/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx
new file mode 100644
index 000000000..bdaf2ecfd
--- /dev/null
+++ b/src/output/plugins/PipeOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PIPE_OUTPUT_PLUGIN_HXX
+#define MPD_PIPE_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin pipe_output_plugin;
+
+#endif
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
new file mode 100644
index 000000000..120bad090
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -0,0 +1,882 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PulseOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
+#include "mixer/plugins/PulseMixerPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#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>
+#include <stdlib.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+struct PulseOutput {
+ AudioOutput 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;
+
+ PulseOutput()
+ :base(pulse_output_plugin) {}
+};
+
+static constexpr Domain pulse_output_domain("pulse_output");
+
+static void
+SetError(Error &error, pa_context *context, const char *msg)
+{
+ const int e = pa_context_errno(context);
+ error.Format(pulse_output_domain, e, "%s: %s", msg, pa_strerror(e));
+}
+
+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.mixer == 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.mixer == &pm);
+
+ po.mixer = nullptr;
+}
+
+bool
+pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume,
+ Error &error)
+{
+ pa_operation *o;
+
+ if (po.context == nullptr || po.stream == nullptr ||
+ pa_stream_get_state(po.stream) != PA_STREAM_READY) {
+ error.Set(pulse_output_domain, "disconnected");
+ return false;
+ }
+
+ o = pa_context_set_sink_input_volume(po.context,
+ pa_stream_get_index(po.stream),
+ volume, nullptr, nullptr);
+ if (o == nullptr) {
+ SetError(error, po.context,
+ "failed to set PulseAudio volume");
+ 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)
+{
+ assert(mainloop != nullptr);
+ assert(operation != nullptr);
+
+ pa_operation_state_t state;
+ while ((state = pa_operation_get_state(operation))
+ == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(mainloop);
+
+ 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(gcc_unused pa_stream *s,
+ gcc_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, Error &error)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ if (pa_context_connect(po->context, po->server,
+ (pa_context_flags_t)0, nullptr) < 0) {
+ SetError(error, po->context,
+ "pa_context_connect() has failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the stream.
+ */
+static void
+pulse_output_delete_stream(PulseOutput *po)
+{
+ assert(po != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_stream_set_suspended_callback(po->stream, nullptr, nullptr);
+
+ 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, Error &error)
+{
+ 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) {
+ error.Set(pulse_output_domain, "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)) {
+ pulse_output_delete_context(po);
+ return false;
+ }
+
+ return true;
+}
+
+static AudioOutput *
+pulse_output_init(const config_param &param, Error &error)
+{
+ PulseOutput *po;
+
+ setenv("PULSE_PROP_media.role", "music", true);
+ setenv("PULSE_PROP_application.icon_name", "mpd", true);
+
+ po = new PulseOutput();
+ if (!po->base.Configure(param, error)) {
+ 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(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ delete po;
+}
+
+static bool
+pulse_output_enable(AudioOutput *ao, Error &error)
+{
+ 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) {
+ error.Set(pulse_output_domain,
+ "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;
+
+ error.Set(pulse_output_domain,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ /* create the libpulse context and connect it */
+
+ if (!pulse_output_setup_context(po, error)) {
+ 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(AudioOutput *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, Error &error)
+{
+ assert(po->mainloop != nullptr);
+
+ pa_context_state_t state;
+
+ if (po->context == nullptr && !pulse_output_setup_context(po, error))
+ 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 */
+ SetError(error, po->context, "failed to connect");
+ 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;
+ }
+ }
+}
+
+static void
+pulse_output_stream_suspended_cb(gcc_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);
+}
+
+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(gcc_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,
+ Error &error)
+{
+ assert(po != nullptr);
+ assert(po->context != nullptr);
+
+ po->stream = pa_stream_new(po->context, po->name, ss, nullptr);
+ if (po->stream == nullptr) {
+ SetError(error, po->context, "pa_stream_new() has failed");
+ return false;
+ }
+
+ pa_stream_set_suspended_callback(po->stream,
+ pulse_output_stream_suspended_cb, po);
+
+ 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(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ pa_sample_spec ss;
+
+ 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)) {
+ 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)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ /* .. and connect it (asynchronously) */
+
+ if (pa_stream_connect_playback(po->stream, po->sink,
+ nullptr, pa_stream_flags_t(0),
+ nullptr, nullptr) < 0) {
+ pulse_output_delete_stream(po);
+
+ SetError(error, po->context,
+ "pa_stream_connect_playback() has failed");
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_close(AudioOutput *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) {
+ FormatWarning(pulse_output_domain,
+ "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, Error &error)
+{
+ 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:
+ SetError(error, po->context,
+ "failed to connect the stream");
+ return false;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(PulseOutput *po, bool pause,
+ Error &error)
+{
+ 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) {
+ SetError(error, po->context, "pa_stream_cork() has failed");
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ SetError(error, po->context, "pa_stream_cork() has failed");
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned
+pulse_output_delay(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+ unsigned result = 0;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->base.pause && pa_stream_is_corked(po->stream) &&
+ 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(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ 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)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != nullptr);
+
+ /* unpause if previously paused */
+
+ if (pa_stream_is_corked(po->stream) &&
+ !pulse_output_stream_pause(po, false, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ if (pa_stream_is_suspended(po->stream)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ error.Set(pulse_output_domain, "suspended");
+ return 0;
+ }
+
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ error.Set(pulse_output_domain, "disconnected");
+ return 0;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ int result = pa_stream_write(po->stream, chunk, size, nullptr,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (result < 0) {
+ SetError(error, po->context, "pa_stream_write() failed");
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(AudioOutput *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) {
+ FormatWarning(pulse_output_domain,
+ "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(AudioOutput *ao)
+{
+ PulseOutput *po = (PulseOutput *)ao;
+
+ assert(po->mainloop != nullptr);
+ assert(po->stream != nullptr);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ Error error;
+ if (!pulse_output_wait_stream(po, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ LogError(error);
+ return false;
+ }
+
+ assert(po->context != nullptr);
+
+ /* cork the stream */
+
+ if (!pa_stream_is_corked(po->stream) &&
+ !pulse_output_stream_pause(po, true, error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ LogError(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, IgnoreError());
+ if (po == nullptr)
+ return false;
+
+ success = pulse_output_wait_connection(po, IgnoreError());
+ pulse_output_finish(&po->base);
+
+ return success;
+}
+
+const struct AudioOutputPlugin 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/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx
new file mode 100644
index 000000000..9219780a5
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PULSE_OUTPUT_PLUGIN_HXX
+#define MPD_PULSE_OUTPUT_PLUGIN_HXX
+
+struct PulseOutput;
+class PulseMixer;
+struct pa_cvolume;
+class Error;
+
+extern const struct AudioOutputPlugin pulse_output_plugin;
+
+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 pa_cvolume *volume, Error &error);
+
+#endif
diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx
new file mode 100644
index 000000000..87e23f55a
--- /dev/null
+++ b/src/output/plugins/RecorderOutputPlugin.cxx
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RecorderOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/fd_util.h"
+#include "open.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+struct RecorderOutput {
+ AudioOutput 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];
+
+ RecorderOutput()
+ :base(recorder_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+
+ bool WriteToFile(const void *data, size_t length, Error &error);
+
+ /**
+ * Writes pending data from the encoder to the output file.
+ */
+ bool EncoderToFile(Error &error);
+};
+
+static constexpr Domain recorder_output_domain("recorder_output");
+
+inline bool
+RecorderOutput::Configure(const config_param &param, Error &error)
+{
+ /* read configuration */
+
+ const char *encoder_name =
+ param.GetBlockValue("encoder", "vorbis");
+ const auto encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == nullptr) {
+ error.Format(config_domain,
+ "No such encoder: %s", encoder_name);
+ return false;
+ }
+
+ path = param.GetBlockValue("path");
+ if (path == nullptr) {
+ error.Set(config_domain, "'path' not configured");
+ return false;
+ }
+
+ /* initialize encoder */
+
+ encoder = encoder_init(*encoder_plugin, param, error);
+ if (encoder == nullptr)
+ return false;
+
+ return true;
+}
+
+static AudioOutput *
+recorder_output_init(const config_param &param, Error &error)
+{
+ RecorderOutput *recorder = new RecorderOutput();
+
+ if (!recorder->Initialize(param, error)) {
+ delete recorder;
+ return nullptr;
+ }
+
+ if (!recorder->Configure(param, error)) {
+ delete recorder;
+ return nullptr;
+ }
+
+ return &recorder->base;
+}
+
+static void
+recorder_output_finish(AudioOutput *ao)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ encoder_finish(recorder->encoder);
+ delete recorder;
+}
+
+inline bool
+RecorderOutput::WriteToFile(const void *_data, size_t length, Error &error)
+{
+ 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 */
+ error.Set(recorder_output_domain,
+ "write() returned 0");
+ return false;
+ } else if (errno != EINTR) {
+ error.FormatErrno("Failed to write to '%s'", path);
+ return false;
+ }
+ }
+}
+
+inline bool
+RecorderOutput::EncoderToFile(Error &error)
+{
+ 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))
+ return false;
+ }
+}
+
+static bool
+recorder_output_open(AudioOutput *ao,
+ AudioFormat &audio_format,
+ Error &error)
+{
+ 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) {
+ error.FormatErrno("Failed to create '%s'", recorder->path);
+ return false;
+ }
+
+ /* open the encoder */
+
+ if (!encoder_open(recorder->encoder, audio_format, error)) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ if (!recorder->EncoderToFile(error)) {
+ encoder_close(recorder->encoder);
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(AudioOutput *ao)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_end(recorder->encoder, IgnoreError()))
+ recorder->EncoderToFile(IgnoreError());
+
+ /* now really close everything */
+
+ encoder_close(recorder->encoder);
+
+ close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ RecorderOutput *recorder = (RecorderOutput *)ao;
+
+ return encoder_write(recorder->encoder, chunk, size, error) &&
+ recorder->EncoderToFile(error)
+ ? size : 0;
+}
+
+const struct AudioOutputPlugin 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/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx
new file mode 100644
index 000000000..ea1044e0f
--- /dev/null
+++ b/src/output/plugins/RecorderOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RECORDER_OUTPUT_PLUGIN_HXX
+#define MPD_RECORDER_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin recorder_output_plugin;
+
+#endif
diff --git a/src/output/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx
new file mode 100644
index 000000000..aa37c91b7
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2003-2014 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 "mixer/MixerList.hxx"
+#include "thread/Mutex.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <string>
+
+/* libroar/services.h declares roar_service_stream::new - work around
+ this C++ problem */
+#define new _new
+#include <roaraudio.h>
+#undef new
+
+class RoarOutput {
+ AudioOutput base;
+
+ std::string host, name;
+
+ roar_vs_t * vss;
+ int err;
+ int role;
+ struct roar_connection con;
+ struct roar_audio_info info;
+ mutable Mutex mutex;
+ bool alive;
+
+public:
+ RoarOutput()
+ :base(roar_output_plugin),
+ err(ROAR_ERROR_NONE) {}
+
+ operator AudioOutput *() {
+ return &base;
+ }
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ void Configure(const config_param &param);
+
+ bool Open(AudioFormat &audio_format, Error &error);
+ void Close();
+
+ void SendTag(const Tag &tag);
+ size_t Play(const void *chunk, size_t size, Error &error);
+ void Cancel();
+
+ int GetVolume() const;
+ bool SetVolume(unsigned volume);
+};
+
+static constexpr Domain roar_output_domain("roar_output");
+
+inline int
+RoarOutput::GetVolume() const
+{
+ const ScopeLock protect(mutex);
+
+ if (vss == nullptr || !alive)
+ return -1;
+
+ float l, r;
+ int error;
+ if (roar_vs_volume_get(vss, &l, &r, &error) < 0)
+ return -1;
+
+ return (l + r) * 50;
+}
+
+int
+roar_output_get_volume(RoarOutput &roar)
+{
+ return roar.GetVolume();
+}
+
+bool
+RoarOutput::SetVolume(unsigned volume)
+{
+ assert(volume <= 100);
+
+ const ScopeLock protect(mutex);
+ if (vss == nullptr || !alive)
+ return false;
+
+ int error;
+ float level = volume / 100.0;
+
+ roar_vs_volume_mono(vss, level, &error);
+ return true;
+}
+
+bool
+roar_output_set_volume(RoarOutput &roar, unsigned volume)
+{
+ return roar.SetVolume(volume);
+}
+
+inline void
+RoarOutput::Configure(const config_param &param)
+{
+ host = param.GetBlockValue("server", "");
+ name = param.GetBlockValue("name", "MPD");
+
+ const char *_role = param.GetBlockValue("role", "music");
+ role = _role != nullptr
+ ? roar_str2role(_role)
+ : ROAR_ROLE_MUSIC;
+}
+
+static AudioOutput *
+roar_init(const config_param &param, Error &error)
+{
+ RoarOutput *self = new RoarOutput();
+
+ if (!self->Initialize(param, error)) {
+ delete self;
+ return nullptr;
+ }
+
+ self->Configure(param);
+ return *self;
+}
+
+static void
+roar_finish(AudioOutput *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ 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;
+ }
+}
+
+inline bool
+RoarOutput::Open(AudioFormat &audio_format, Error &error)
+{
+ const ScopeLock protect(mutex);
+
+ if (roar_simple_connect(&con,
+ host.empty() ? nullptr : host.c_str(),
+ name.c_str()) < 0) {
+ error.Set(roar_output_domain,
+ "Failed to connect to Roar server");
+ return false;
+ }
+
+ vss = roar_vs_new_from_con(&con, &err);
+
+ if (vss == nullptr || err != ROAR_ERROR_NONE) {
+ error.Set(roar_output_domain, "Failed to connect to server");
+ return false;
+ }
+
+ roar_use_audio_format(&info, audio_format);
+
+ if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) {
+ error.Set(roar_output_domain, "Failed to start stream");
+ return false;
+ }
+
+ roar_vs_role(vss, role, &err);
+ alive = true;
+ return true;
+}
+
+static bool
+roar_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ return self->Open(audio_format, error);
+}
+
+inline void
+RoarOutput::Close()
+{
+ const ScopeLock protect(mutex);
+
+ alive = false;
+
+ if (vss != nullptr)
+ roar_vs_close(vss, ROAR_VS_TRUE, &err);
+ vss = nullptr;
+ roar_disconnect(&con);
+}
+
+static void
+roar_close(AudioOutput *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ self->Close();
+}
+
+inline void
+RoarOutput::Cancel()
+{
+ const ScopeLock protect(mutex);
+
+ if (vss == nullptr)
+ return;
+
+ roar_vs_t *_vss = vss;
+ vss = nullptr;
+ roar_vs_close(_vss, ROAR_VS_TRUE, &err);
+ alive = false;
+
+ _vss = roar_vs_new_from_con(&con, &err);
+ if (_vss == nullptr)
+ return;
+
+ if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) {
+ roar_vs_close(_vss, ROAR_VS_TRUE, &err);
+ LogError(roar_output_domain, "Failed to start stream");
+ return;
+ }
+
+ roar_vs_role(_vss, role, &err);
+ vss = _vss;
+ alive = true;
+}
+
+static void
+roar_cancel(AudioOutput *ao)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+
+ self->Cancel();
+}
+
+inline size_t
+RoarOutput::Play(const void *chunk, size_t size, Error &error)
+{
+ if (vss == nullptr) {
+ error.Set(roar_output_domain, "Connection is invalid");
+ return 0;
+ }
+
+ ssize_t nbytes = roar_vs_write(vss, chunk, size, &err);
+ if (nbytes <= 0) {
+ error.Set(roar_output_domain, "Failed to play data");
+ return 0;
+ }
+
+ return nbytes;
+}
+
+static size_t
+roar_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ return self->Play(chunk, size, error);
+}
+
+static const char*
+roar_tag_convert(TagType 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";
+ case TAG_MUSICBRAINZ_RELEASETRACKID:
+ *is_uuid = true;
+ return "HASH";
+
+ default:
+ return nullptr;
+ }
+}
+
+inline void
+RoarOutput::SendTag(const Tag &tag)
+{
+ if (vss == nullptr)
+ return;
+
+ const ScopeLock protect(mutex);
+
+ size_t cnt = 0;
+ struct roar_keyval vals[32];
+ char uuid_buf[32][64];
+
+ char timebuf[16];
+ if (!tag.duration.IsNegative()) {
+ const unsigned seconds = tag.duration.ToS();
+ snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
+ seconds / 3600, (seconds % 3600) / 60, seconds % 60);
+
+ vals[cnt].key = const_cast<char *>("LENGTH");
+ vals[cnt].value = timebuf;
+ ++cnt;
+ }
+
+ for (const auto &item : tag) {
+ if (cnt >= 32)
+ break;
+
+ bool is_uuid = false;
+ const char *key = roar_tag_convert(item.type,
+ &is_uuid);
+ if (key != nullptr) {
+ vals[cnt].key = const_cast<char *>(key);
+
+ if (is_uuid) {
+ snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s",
+ item.value);
+ vals[cnt].value = uuid_buf[cnt];
+ } else {
+ vals[cnt].value = const_cast<char *>(item.value);
+ }
+
+ cnt++;
+ }
+ }
+
+ roar_vs_meta(vss, vals, cnt, &(err));
+}
+
+static void
+roar_send_tag(AudioOutput *ao, const Tag *meta)
+{
+ RoarOutput *self = (RoarOutput *)ao;
+ self->SendTag(*meta);
+}
+
+const struct AudioOutputPlugin 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/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx
new file mode 100644
index 000000000..5f5a9246e
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ROAR_OUTPUT_PLUGIN_H
+#define MPD_ROAR_OUTPUT_PLUGIN_H
+
+class RoarOutput;
+
+extern const struct AudioOutputPlugin 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/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx
new file mode 100644
index 000000000..b51f7ed82
--- /dev/null
+++ b/src/output/plugins/ShoutOutputPlugin.cxx
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ShoutOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/FatalError.hxx"
+#include "Log.hxx"
+
+#include <shout/shout.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
+
+struct ShoutOutput final {
+ AudioOutput base;
+
+ shout_t *shout_conn;
+ shout_metadata_t *shout_meta;
+
+ Encoder *encoder;
+
+ float quality;
+ int bitrate;
+
+ int timeout;
+
+ uint8_t buffer[32768];
+
+ ShoutOutput()
+ :base(shout_output_plugin),
+ 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, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+};
+
+static int shout_init_count;
+
+static constexpr Domain shout_output_domain("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)
+ FormatFatalError("no \"%s\" defined for shout device defined "
+ "at line %u\n", name, param.line);
+
+ return value;
+}
+
+inline bool
+ShoutOutput::Configure(const config_param &param, Error &error)
+{
+
+ const AudioFormat audio_format = base.config_audio_format;
+ if (!audio_format.IsFullyDefined()) {
+ error.Set(config_domain,
+ "Need full audio format specification");
+ return false;
+ }
+
+ 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) {
+ error.Set(config_domain, "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) {
+ error.Format(config_domain,
+ "shout quality \"%s\" is not a number in the "
+ "range -1 to 10",
+ value);
+ return false;
+ }
+
+ if (param.GetBlockValue("bitrate") != nullptr) {
+ error.Set(config_domain,
+ "quality and bitrate are "
+ "both defined");
+ return false;
+ }
+ } else {
+ value = param.GetBlockValue("bitrate");
+ if (value == nullptr) {
+ error.Set(config_domain,
+ "neither bitrate nor quality defined");
+ return false;
+ }
+
+ char *test;
+ bitrate = strtol(value, &test, 10);
+
+ if (*test != '\0' || bitrate <= 0) {
+ error.Set(config_domain,
+ "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) {
+ error.Format(config_domain,
+ "couldn't find shout encoder plugin \"%s\"",
+ encoding);
+ return false;
+ }
+
+ encoder = encoder_init(*encoder_plugin, param, error);
+ 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")) {
+ error.Format(config_domain,
+ "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 {
+ error.Format(config_domain,
+ "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) {
+ error.Set(shout_output_domain, 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)) {
+ error.Set(shout_output_domain, shout_get_error(shout_conn));
+ return false;
+ }
+
+ value = param.GetBlockValue("description");
+ if (value != nullptr && shout_set_description(shout_conn, value)) {
+ error.Set(shout_output_domain, shout_get_error(shout_conn));
+ return false;
+ }
+
+ value = param.GetBlockValue("url");
+ if (value != nullptr && shout_set_url(shout_conn, value)) {
+ error.Set(shout_output_domain, 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 AudioOutput *
+my_shout_init_driver(const config_param &param, Error &error)
+{
+ ShoutOutput *sd = new ShoutOutput();
+ if (!sd->Initialize(param, error)) {
+ delete sd;
+ return nullptr;
+ }
+
+ if (!sd->Configure(param, error)) {
+ 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, Error &error)
+{
+ switch (err) {
+ case SHOUTERR_SUCCESS:
+ break;
+
+ case SHOUTERR_UNCONNECTED:
+ case SHOUTERR_SOCKET:
+ error.Format(shout_output_domain, 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:
+ error.Format(shout_output_domain, 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, Error &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, IgnoreError()))
+ write_page(sd, IgnoreError());
+
+ encoder_close(sd->encoder);
+ }
+
+ if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
+ shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
+ FormatWarning(shout_output_domain,
+ "problem closing connection to shout server: %s",
+ shout_get_error(sd->shout_conn));
+ }
+}
+
+static void
+my_shout_finish_driver(AudioOutput *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ encoder_finish(sd->encoder);
+
+ delete sd;
+
+ shout_init_count--;
+
+ if (shout_init_count == 0)
+ shout_shutdown();
+}
+
+static void
+my_shout_drop_buffered_audio(AudioOutput *ao)
+{
+ gcc_unused
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ /* needs to be implemented for shout */
+}
+
+static void
+my_shout_close_device(AudioOutput *ao)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ close_shout_conn(sd);
+}
+
+static bool
+shout_connect(ShoutOutput *sd, Error &error)
+{
+ switch (shout_open(sd->shout_conn)) {
+ case SHOUTERR_SUCCESS:
+ case SHOUTERR_CONNECTED:
+ return true;
+
+ default:
+ error.Format(shout_output_domain,
+ "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(AudioOutput *ao, AudioFormat &audio_format,
+ Error &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(AudioOutput *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(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ return encoder_write(sd->encoder, chunk, size, error) &&
+ write_page(sd, error)
+ ? size
+ : 0;
+}
+
+static bool
+my_shout_pause(AudioOutput *ao)
+{
+ static char silence[1020];
+
+ return my_shout_play(ao, silence, sizeof(silence), IgnoreError());
+}
+
+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 (const auto &item : *tag) {
+ switch (item.type) {
+ case TAG_ARTIST:
+ strncpy(artist, item.value, size);
+ break;
+ case TAG_TITLE:
+ strncpy(title, item.value, size);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ snprintf(dest, size, "%s - %s", artist, title);
+}
+
+static void my_shout_set_tag(AudioOutput *ao,
+ const Tag *tag)
+{
+ ShoutOutput *sd = (ShoutOutput *)ao;
+
+ if (sd->encoder->plugin.tag != nullptr) {
+ /* encoder plugin supports stream tags */
+
+ Error error;
+ if (!encoder_pre_tag(sd->encoder, error) ||
+ !write_page(sd, error) ||
+ !encoder_tag(sd->encoder, tag, error)) {
+ LogError(error);
+ return;
+ }
+ } 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)) {
+ LogWarning(shout_output_domain,
+ "error setting shout metadata");
+ }
+ }
+
+ write_page(sd, IgnoreError());
+}
+
+const struct AudioOutputPlugin 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/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx
new file mode 100644
index 000000000..9f706fc3b
--- /dev/null
+++ b/src/output/plugins/ShoutOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SHOUT_OUTPUT_PLUGIN_HXX
+#define MPD_SHOUT_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin shout_output_plugin;
+
+#endif
diff --git a/src/output/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx
new file mode 100644
index 000000000..30745f97c
--- /dev/null
+++ b/src/output/plugins/SolarisOutputPlugin.cxx
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SolarisOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#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
+
+struct SolarisOutput {
+ AudioOutput base;
+
+ /* configuration */
+ const char *device;
+
+ int fd;
+
+ SolarisOutput()
+ :base(solaris_output_plugin) {}
+
+ bool Initialize(const config_param &param, Error &error_r) {
+ return base.Configure(param, error_r);
+ }
+};
+
+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 AudioOutput *
+solaris_output_init(const config_param &param, Error &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(AudioOutput *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ delete so;
+}
+
+static bool
+solaris_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &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) {
+ error.FormatErrno("Failed to open %s",
+ so->device);
+ 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) {
+ error.SetErrno("AUDIO_GETINFO failed");
+ 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) {
+ error.SetErrno("AUDIO_SETINFO failed");
+ close(so->fd);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+solaris_output_close(AudioOutput *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ close(so->fd);
+}
+
+static size_t
+solaris_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+ ssize_t nbytes;
+
+ nbytes = write(so->fd, chunk, size);
+ if (nbytes <= 0) {
+ error.SetErrno("Write failed");
+ return 0;
+ }
+
+ return nbytes;
+}
+
+static void
+solaris_output_cancel(AudioOutput *ao)
+{
+ SolarisOutput *so = (SolarisOutput *)ao;
+
+ ioctl(so->fd, I_FLUSH);
+}
+
+const struct AudioOutputPlugin 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/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx
new file mode 100644
index 000000000..3f9ede7a6
--- /dev/null
+++ b/src/output/plugins/SolarisOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOLARIS_OUTPUT_PLUGIN_HXX
+#define MPD_SOLARIS_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin solaris_output_plugin;
+
+#endif
diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx
new file mode 100644
index 000000000..e5c5a6f0c
--- /dev/null
+++ b/src/output/plugins/WinmmOutputPlugin.cxx
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "WinmmOutputPlugin.hxx"
+#include "../OutputAPI.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "mixer/MixerList.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+
+#include <stdlib.h>
+#include <string.h>
+
+struct WinmmBuffer {
+ PcmBuffer buffer;
+
+ WAVEHDR hdr;
+};
+
+struct WinmmOutput {
+ AudioOutput 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;
+
+ WinmmOutput()
+ :base(winmm_output_plugin) {}
+};
+
+static constexpr Domain winmm_output_domain("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, Error &error)
+{
+ /* 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:
+ error.Format(winmm_output_domain,
+ "device \"%s\" is not found", device_name);
+ return false;
+}
+
+static AudioOutput *
+winmm_output_init(const config_param &param, Error &error)
+{
+ WinmmOutput *wo = new WinmmOutput();
+ if (!wo->base.Configure(param, error)) {
+ delete wo;
+ return nullptr;
+ }
+
+ const char *device = param.GetBlockValue("device");
+ if (!get_device_id(device, &wo->device_id, error)) {
+ delete wo;
+ return nullptr;
+ }
+
+ return &wo->base;
+}
+
+static void
+winmm_output_finish(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ delete wo;
+}
+
+static bool
+winmm_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ wo->event = CreateEvent(nullptr, false, false, nullptr);
+ if (wo->event == nullptr) {
+ error.Set(winmm_output_domain, "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);
+ error.Set(winmm_output_domain, "waveOutOpen() failed");
+ return false;
+ }
+
+ for (unsigned i = 0; i < ARRAY_SIZE(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(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ for (unsigned i = 0; i < ARRAY_SIZE(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,
+ Error &error)
+{
+ 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) {
+ error.Set(winmm_output_domain, result,
+ "waveOutPrepareHeader() failed");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wait until the buffer is finished.
+ */
+static bool
+winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
+ Error &error)
+{
+ 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) {
+ error.Set(winmm_output_domain, result,
+ "waveOutUnprepareHeader() failed");
+ return false;
+ }
+
+ /* wait some more */
+ WaitForSingleObject(wo->event, INFINITE);
+ }
+}
+
+static size_t
+winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error)
+{
+ 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) ||
+ !winmm_set_buffer(wo, buffer, chunk, size, error))
+ 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));
+ error.Set(winmm_output_domain, result,
+ "waveOutWrite() failed");
+ return 0;
+ }
+
+ /* mark our buffer as "used" */
+ wo->next_buffer = (wo->next_buffer + 1) %
+ ARRAY_SIZE(wo->buffers);
+
+ return size;
+}
+
+static bool
+winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
+{
+ for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
+ return false;
+
+ for (unsigned i = 0; i < wo->next_buffer; ++i)
+ if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
+ return false;
+
+ return true;
+}
+
+static void
+winmm_stop(WinmmOutput *wo)
+{
+ waveOutReset(wo->handle);
+
+ for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
+ WinmmBuffer *buffer = &wo->buffers[i];
+ waveOutUnprepareHeader(wo->handle, &buffer->hdr,
+ sizeof(buffer->hdr));
+ }
+}
+
+static void
+winmm_output_drain(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ if (!winmm_drain_all_buffers(wo, IgnoreError()))
+ winmm_stop(wo);
+}
+
+static void
+winmm_output_cancel(AudioOutput *ao)
+{
+ WinmmOutput *wo = (WinmmOutput *)ao;
+
+ winmm_stop(wo);
+}
+
+const struct AudioOutputPlugin 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/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx
new file mode 100644
index 000000000..50fae4f2f
--- /dev/null
+++ b/src/output/plugins/WinmmOutputPlugin.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_WINMM_OUTPUT_PLUGIN_HXX
+#define MPD_WINMM_OUTPUT_PLUGIN_HXX
+
+#include "check.h"
+
+#ifdef ENABLE_WINMM_OUTPUT
+
+#include "Compiler.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+
+struct WinmmOutput;
+
+extern const struct AudioOutputPlugin winmm_output_plugin;
+
+gcc_pure
+HWAVEOUT
+winmm_output_get_handle(WinmmOutput &output);
+
+#endif
+
+#endif
diff --git a/src/output/plugins/httpd/HttpdClient.cxx b/src/output/plugins/httpd/HttpdClient.cxx
new file mode 100644
index 000000000..3797c3d26
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdClient.cxx
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdClient.hxx"
+#include "HttpdInternal.hxx"
+#include "util/ASCII.hxx"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "system/SocketError.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+HttpdClient::~HttpdClient()
+{
+ if (state == RESPONSE) {
+ if (current_page != nullptr)
+ current_page->Unref();
+
+ ClearQueue();
+ }
+
+ if (metadata)
+ metadata->Unref();
+
+ if (IsDefined())
+ BufferedSocket::Close();
+}
+
+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;
+
+ if (!head_method)
+ httpd.SendHeader(*this);
+}
+
+/**
+ * Handle a line of the HTTP request.
+ */
+bool
+HttpdClient::HandleLine(const char *line)
+{
+ assert(state != RESPONSE);
+
+ if (state == REQUEST) {
+ if (memcmp(line, "HEAD /", 6) == 0) {
+ line += 6;
+ head_method = true;
+ } else if (memcmp(line, "GET /", 5) == 0) {
+ line += 5;
+ } else {
+ /* only GET is supported */
+ LogWarning(httpd_output_domain,
+ "malformed request line from client");
+ return false;
+ }
+
+ line = strchr(line, ' ');
+ if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
+ /* HTTP/0.9 without request headers */
+
+ if (head_method)
+ return false;
+
+ 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 (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
+ StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
+ /* Send icy metadata */
+ metadata_requested = metadata_supported;
+ return true;
+ }
+
+ if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
+ /* Send as dlna */
+ dlna_streaming_requested = true;
+ /* metadata is not supported by dlna streaming, so disable it */
+ metadata_supported = false;
+ metadata_requested = false;
+ return true;
+ }
+
+ /* expect more request headers */
+ return true;
+ }
+}
+
+/**
+ * Sends the status line and response headers to the client.
+ */
+bool
+HttpdClient::SendResponse()
+{
+ char buffer[1024], *allocated = nullptr;
+ const char *response;
+
+ assert(state == RESPONSE);
+
+ if (dlna_streaming_requested) {
+ snprintf(buffer, sizeof(buffer),
+ "HTTP/1.1 206 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: 10000\r\n"
+ "Content-RangeX: 0-1000000/1000000\r\n"
+ "transferMode.dlna.org: Streaming\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Connection: close\r\n"
+ "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
+ "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
+ "\r\n",
+ httpd.content_type);
+ response = buffer;
+
+ } else if (metadata_requested) {
+ response = allocated =
+ icy_server_metadata_header(httpd.name, httpd.genre,
+ httpd.website,
+ httpd.content_type,
+ metaint);
+ } else { /* revert to a normal HTTP request */
+ 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);
+ response = buffer;
+ }
+
+ ssize_t nbytes = SocketMonitor::Write(response, strlen(response));
+ delete[] allocated;
+ if (gcc_unlikely(nbytes < 0)) {
+ const SocketErrorMessage msg;
+ FormatWarning(httpd_output_domain,
+ "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),
+ queue_size(0),
+ head_method(false),
+ 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)
+{
+}
+
+void
+HttpdClient::ClearQueue()
+{
+ assert(state == RESPONSE);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+#ifndef NDEBUG
+ assert(queue_size >= page->size);
+ queue_size -= page->size;
+#endif
+
+ page->Unref();
+ }
+
+ assert(queue_size == 0);
+}
+
+void
+HttpdClient::CancelQueue()
+{
+ if (state != RESPONSE)
+ return;
+
+ ClearQueue();
+
+ 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();
+ current_position = 0;
+
+ assert(queue_size >= current_page->size);
+ queue_size -= current_page->size;
+ }
+
+ 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);
+ FormatWarning(httpd_output_domain,
+ "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 {
+ char 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);
+ FormatWarning(httpd_output_domain,
+ "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);
+ FormatWarning(httpd_output_domain,
+ "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;
+
+ if (queue_size > 256 * 1024) {
+ FormatDebug(httpd_output_domain,
+ "client is too slow, flushing its queue");
+ ClearQueue();
+ }
+
+ page->Ref();
+ pages.push(page);
+ queue_size += page->size;
+
+ ScheduleWrite();
+}
+
+void
+HttpdClient::PushMetaData(Page *page)
+{
+ assert(page != nullptr);
+
+ if (metadata) {
+ metadata->Unref();
+ metadata = nullptr;
+ }
+
+ 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(void *data, size_t length)
+{
+ if (state == RESPONSE) {
+ LogWarning(httpd_output_domain,
+ "unexpected input from client");
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ char *line = (char *)data;
+ char *newline = (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 */
+ *newline = 0;
+
+ if (!HandleLine(line)) {
+ LockClose();
+ return InputResult::CLOSED;
+ }
+
+ if (state == RESPONSE) {
+ if (!SendResponse())
+ return InputResult::CLOSED;
+
+ if (head_method) {
+ LockClose();
+ return InputResult::CLOSED;
+ }
+ }
+
+ return InputResult::AGAIN;
+}
+
+void
+HttpdClient::OnSocketError(Error &&error)
+{
+ LogError(error);
+}
+
+void
+HttpdClient::OnSocketClosed()
+{
+ LockClose();
+}
diff --git a/src/output/plugins/httpd/HttpdClient.hxx b/src/output/plugins/httpd/HttpdClient.hxx
new file mode 100644
index 000000000..f94f05769
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdClient.hxx
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX
+#define MPD_OUTPUT_HTTPD_CLIENT_HXX
+
+#include "event/BufferedSocket.hxx"
+#include "Compiler.h"
+
+#include <queue>
+#include <list>
+
+#include <stddef.h>
+
+class HttpdOutput;
+class Page;
+
+class HttpdClient final : BufferedSocket {
+ /**
+ * The httpd output object this client is connected to.
+ */
+ HttpdOutput &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::queue<Page *, std::list<Page *>> pages;
+
+ /**
+ * The sum of all page sizes in #pages.
+ */
+ size_t queue_size;
+
+ /**
+ * The #page which is currently being sent to the client.
+ */
+ Page *current_page;
+
+ /**
+ * The amount of bytes which were already sent from
+ * #current_page.
+ */
+ size_t current_position;
+
+ /**
+ * Is this a HEAD request?
+ */
+ bool head_method;
+
+ /**
+ * 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
+ */
+ unsigned 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.
+ */
+ unsigned 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();
+
+ /**
+ * 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);
+
+private:
+ void ClearQueue();
+
+protected:
+ virtual bool OnSocketReady(unsigned flags) override;
+ virtual InputResult OnSocketInput(void *data, size_t length) override;
+ virtual void OnSocketError(Error &&error) override;
+ virtual void OnSocketClosed() override;
+};
+
+#endif
diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx
new file mode 100644
index 000000000..20ff15e42
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * Internal declarations for the "httpd" audio output plugin.
+ */
+
+#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
+#define MPD_OUTPUT_HTTPD_INTERNAL_H
+
+#include "output/Internal.hxx"
+#include "output/Timer.hxx"
+#include "thread/Mutex.hxx"
+#include "event/ServerSocket.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Cast.hxx"
+#include "Compiler.h"
+
+#ifdef _LIBCPP_VERSION
+/* can't use incomplete template arguments with libc++ */
+#include "HttpdClient.hxx"
+#endif
+
+#include <forward_list>
+#include <queue>
+#include <list>
+
+struct config_param;
+class Error;
+class EventLoop;
+class ServerSocket;
+class HttpdClient;
+class Page;
+struct Encoder;
+struct Tag;
+
+class HttpdOutput final : ServerSocket, DeferredMonitor {
+ AudioOutput 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;
+
+public:
+ /**
+ * The MIME type produced by the #encoder.
+ */
+ const char *content_type;
+
+ /**
+ * This mutex protects the listener socket and the client
+ * list.
+ */
+ mutable Mutex mutex;
+
+ /**
+ * This condition gets signalled when an item is removed from
+ * #pages.
+ */
+ Cond cond;
+
+private:
+ /**
+ * A #Timer object to synchronize this output with the
+ * wallclock.
+ */
+ 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 page queue, i.e. pages from the encoder to be
+ * broadcasted to all clients. This container is necessary to
+ * pass pages from the OutputThread to the IOThread. It is
+ * protected by #mutex, and removing signals #cond.
+ */
+ std::queue<Page *, std::list<Page *>> pages;
+
+ public:
+ /**
+ * The configured name.
+ */
+ char const *name;
+ /**
+ * The configured genre.
+ */
+ char const *genre;
+ /**
+ * The configured website address.
+ */
+ char const *website;
+
+private:
+ /**
+ * 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.
+ */
+ unsigned clients_max, clients_cnt;
+
+public:
+ HttpdOutput(EventLoop &_loop);
+ ~HttpdOutput();
+
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+ constexpr
+#endif
+ static HttpdOutput *Cast(AudioOutput *ao) {
+ return &ContainerCast(*ao, &HttpdOutput::base);
+ }
+
+ using DeferredMonitor::GetEventLoop;
+
+ bool Init(const config_param &param, Error &error);
+
+ bool Configure(const config_param &param, Error &error);
+
+ AudioOutput *InitAndConfigure(const config_param &param,
+ Error &error) {
+ if (!Init(param, error))
+ return nullptr;
+
+ if (!Configure(param, error))
+ return nullptr;
+
+ return &base;
+ }
+
+ bool Bind(Error &error);
+ void Unbind();
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool OpenEncoder(AudioFormat &audio_format, Error &error);
+
+ /**
+ * Caller must lock the mutex.
+ */
+ bool Open(AudioFormat &audio_format, Error &error);
+
+ /**
+ * 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;
+
+ gcc_pure
+ unsigned Delay() 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, Error &error);
+
+ void SendTag(const Tag *tag);
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+ void CancelAllClients();
+
+private:
+ virtual void RunDeferred() override;
+
+ virtual void OnAccept(int fd, const sockaddr &address,
+ size_t address_length, int uid) override;
+};
+
+extern const class Domain httpd_output_domain;
+
+#endif
diff --git a/src/output/plugins/httpd/HttpdOutputPlugin.cxx b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
new file mode 100644
index 000000000..e3ba7727d
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.cxx
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "HttpdOutputPlugin.hxx"
+#include "HttpdInternal.hxx"
+#include "HttpdClient.hxx"
+#include "output/OutputAPI.hxx"
+#include "encoder/EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "system/Resolver.hxx"
+#include "Page.hxx"
+#include "IcyMetaDataServer.hxx"
+#include "system/fd_util.h"
+#include "IOThread.hxx"
+#include "event/Call.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.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
+
+const Domain httpd_output_domain("httpd_output");
+
+inline
+HttpdOutput::HttpdOutput(EventLoop &_loop)
+ :ServerSocket(_loop), DeferredMonitor(_loop),
+ base(httpd_output_plugin),
+ encoder(nullptr), unflushed_input(0),
+ metadata(nullptr)
+{
+}
+
+HttpdOutput::~HttpdOutput()
+{
+ if (metadata != nullptr)
+ metadata->Unref();
+
+ if (encoder != nullptr)
+ encoder_finish(encoder);
+
+}
+
+inline bool
+HttpdOutput::Bind(Error &error)
+{
+ open = false;
+
+ bool result = false;
+ BlockingCall(GetEventLoop(), [this, &error, &result](){
+ result = ServerSocket::Open(error);
+ });
+ return result;
+}
+
+inline void
+HttpdOutput::Unbind()
+{
+ assert(!open);
+
+ BlockingCall(GetEventLoop(), [this](){
+ ServerSocket::Close();
+ });
+}
+
+inline bool
+HttpdOutput::Configure(const config_param &param, Error &error)
+{
+ /* 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");
+
+ unsigned 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 == nullptr) {
+ error.Format(httpd_output_domain,
+ "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 != nullptr &&
+ strcmp(bind_to_address, "any") != 0
+ ? AddHost(bind_to_address, port, error)
+ : AddPort(port, error);
+ if (!success)
+ return false;
+
+ /* initialize encoder */
+
+ encoder = encoder_init(*encoder_plugin, param, error);
+ 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;
+}
+
+inline bool
+HttpdOutput::Init(const config_param &param, Error &error)
+{
+ return base.Configure(param, error);
+}
+
+static AudioOutput *
+httpd_output_init(const config_param &param, Error &error)
+{
+ HttpdOutput *httpd = new HttpdOutput(io_thread_get());
+
+ AudioOutput *result = httpd->InitAndConfigure(param, error);
+ if (result == nullptr)
+ delete httpd;
+
+ return result;
+}
+
+static void
+httpd_output_finish(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ 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::RunDeferred()
+{
+ /* this method runs in the IOThread; it broadcasts pages from
+ our own queue to all clients */
+
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+
+ for (auto &client : clients)
+ client.PushPage(page);
+
+ page->Unref();
+ }
+
+ /* wake up the client that may be waiting for the queue to be
+ flushed */
+ cond.broadcast();
+}
+
+void
+HttpdOutput::OnAccept(int fd, const sockaddr &address,
+ size_t address_length, gcc_unused int uid)
+{
+ /* the listener socket has become readable - a client has
+ connected */
+
+#ifdef HAVE_LIBWRAP
+ if (address.sa_family != AF_UNIX) {
+ const auto hostaddr = sockaddr_to_string(&address,
+ address_length);
+ // TODO: shall we obtain the program name from argv[0]?
+ const char *progname = "mpd";
+
+ struct request_info req;
+ request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
+
+ fromhost(&req);
+
+ if (!hosts_access(&req)) {
+ /* tcp wrappers says no */
+ FormatWarning(httpd_output_domain,
+ "libwrap refused connection (libwrap=%s) from %s",
+ progname, hostaddr.c_str());
+ close_socket(fd);
+ return;
+ }
+ }
+#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) {
+ LogErrno(httpd_output_domain, "accept() failed");
+ }
+}
+
+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, IgnoreError());
+ 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 nullptr;
+
+ return Page::Copy(buffer, size);
+}
+
+static bool
+httpd_output_enable(AudioOutput *ao, Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Bind(error);
+}
+
+static void
+httpd_output_disable(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ httpd->Unbind();
+}
+
+inline bool
+HttpdOutput::OpenEncoder(AudioFormat &audio_format, Error &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, Error &error)
+{
+ assert(!open);
+ assert(clients.empty());
+
+ /* open the encoder */
+
+ if (!OpenEncoder(audio_format, error))
+ return false;
+
+ /* initialize other attributes */
+
+ clients_cnt = 0;
+ timer = new Timer(audio_format);
+
+ open = true;
+
+ return true;
+}
+
+static bool
+httpd_output_open(AudioOutput *ao, AudioFormat &audio_format,
+ Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ const ScopeLock protect(httpd->mutex);
+ return httpd->Open(audio_format, error);
+}
+
+inline void
+HttpdOutput::Close()
+{
+ assert(open);
+
+ open = false;
+
+ delete timer;
+
+ BlockingCall(GetEventLoop(), [this](){
+ clients.clear();
+ });
+
+ if (header != nullptr)
+ header->Unref();
+
+ encoder_close(encoder);
+}
+
+static void
+httpd_output_close(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::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 != nullptr)
+ client.PushPage(header);
+}
+
+inline unsigned
+HttpdOutput::Delay() const
+{
+ if (!LockHasClients() && base.pause) {
+ /* if there's no client and this output is paused,
+ then httpd_output_pause() will not do anything, it
+ will not fill the buffer and it will not update the
+ timer; therefore, we reset the timer here */
+ 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 timer->IsStarted()
+ ? timer->GetDelay()
+ : 0;
+}
+
+static unsigned
+httpd_output_delay(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Delay();
+}
+
+void
+HttpdOutput::BroadcastPage(Page *page)
+{
+ assert(page != nullptr);
+
+ mutex.lock();
+ pages.push(page);
+ page->Ref();
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
+}
+
+void
+HttpdOutput::BroadcastFromEncoder()
+{
+ /* synchronize with the IOThread */
+ mutex.lock();
+ while (!pages.empty())
+ cond.wait(mutex);
+
+ Page *page;
+ while ((page = ReadPage()) != nullptr)
+ pages.push(page);
+
+ mutex.unlock();
+
+ DeferredMonitor::Schedule();
+}
+
+inline bool
+HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error)
+{
+ if (!encoder_write(encoder, chunk, size, error))
+ return false;
+
+ unflushed_input += size;
+
+ BroadcastFromEncoder();
+ return true;
+}
+
+inline size_t
+HttpdOutput::Play(const void *chunk, size_t size, Error &error)
+{
+ if (LockHasClients()) {
+ if (!EncodeAndPlay(chunk, size, error))
+ return 0;
+ }
+
+ if (!timer->IsStarted())
+ timer->Start();
+ timer->Add(size);
+
+ return size;
+}
+
+static size_t
+httpd_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ return httpd->Play(chunk, size, error);
+}
+
+static bool
+httpd_output_pause(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ if (httpd->LockHasClients()) {
+ static const char silence[1020] = { 0 };
+ return httpd_output_play(ao, silence, sizeof(silence),
+ IgnoreError()) > 0;
+ } else {
+ return true;
+ }
+}
+
+inline void
+HttpdOutput::SendTag(const Tag *tag)
+{
+ assert(tag != nullptr);
+
+ if (encoder->plugin.tag != nullptr) {
+ /* embed encoder tags */
+
+ /* flush the current stream, and end it */
+
+ encoder_pre_tag(encoder, IgnoreError());
+ BroadcastFromEncoder();
+
+ /* send the tag to the encoder - which starts a new
+ stream now */
+
+ encoder_tag(encoder, tag, IgnoreError());
+
+ /* 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 != nullptr) {
+ if (header != nullptr)
+ header->Unref();
+ header = page;
+ BroadcastPage(page);
+ }
+ } else {
+ /* use Icy-Metadata */
+
+ if (metadata != nullptr)
+ metadata->Unref();
+
+ static constexpr TagType types[] = {
+ TAG_ALBUM, TAG_ARTIST, TAG_TITLE,
+ TAG_NUM_OF_ITEM_TYPES
+ };
+
+ metadata = icy_server_metadata_page(*tag, &types[0]);
+ if (metadata != nullptr) {
+ const ScopeLock protect(mutex);
+ for (auto &client : clients)
+ client.PushMetaData(metadata);
+ }
+ }
+}
+
+static void
+httpd_output_tag(AudioOutput *ao, const Tag *tag)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ httpd->SendTag(tag);
+}
+
+inline void
+HttpdOutput::CancelAllClients()
+{
+ const ScopeLock protect(mutex);
+
+ while (!pages.empty()) {
+ Page *page = pages.front();
+ pages.pop();
+ page->Unref();
+ }
+
+ for (auto &client : clients)
+ client.CancelQueue();
+
+ cond.broadcast();
+}
+
+static void
+httpd_output_cancel(AudioOutput *ao)
+{
+ HttpdOutput *httpd = HttpdOutput::Cast(ao);
+
+ BlockingCall(io_thread_get(), [httpd](){
+ httpd->CancelAllClients();
+ });
+}
+
+const struct AudioOutputPlugin 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/plugins/httpd/HttpdOutputPlugin.hxx b/src/output/plugins/httpd/HttpdOutputPlugin.hxx
new file mode 100644
index 000000000..df99e2b43
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_HTTPD_OUTPUT_PLUGIN_HXX
+#define MPD_HTTPD_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin httpd_output_plugin;
+
+#endif
diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx
new file mode 100644
index 000000000..146df23d1
--- /dev/null
+++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "IcyMetaDataServer.hxx"
+#include "Page.hxx"
+#include "tag/Tag.hxx"
+#include "util/FormatString.hxx"
+
+#include <glib.h>
+
+#include <string.h>
+
+char*
+icy_server_metadata_header(const char *name,
+ const char *genre, const char *url,
+ const char *content_type, int metaint)
+{
+ return FormatNew("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 = FormatNew("nStreamTitle='%s';"
+ "StreamUrl='%s';",
+ stream_title,
+ stream_url);
+
+ 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) {
+ delete[] icy_metadata;
+ return nullptr;
+ }
+
+ return icy_metadata;
+}
+
+Page *
+icy_server_metadata_page(const Tag &tag, const TagType *types)
+{
+ const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES];
+ gint last_item, item;
+ guint position;
+ gchar *icy_string;
+ gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
+ // "StreamTitle='';StreamUrl='';"
+ // = 4081 - 28
+ stream_title[0] = '\0';
+
+ last_item = -1;
+
+ 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 == nullptr)
+ return nullptr;
+
+ Page *icy_metadata = Page::Copy(icy_string, (icy_string[0] * 16) + 1);
+
+ delete[] icy_string;
+
+ return icy_metadata;
+}
diff --git a/src/output/plugins/httpd/IcyMetaDataServer.hxx b/src/output/plugins/httpd/IcyMetaDataServer.hxx
new file mode 100644
index 000000000..773b46641
--- /dev/null
+++ b/src/output/plugins/httpd/IcyMetaDataServer.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ICY_META_DATA_SERVER_HXX
+#define MPD_ICY_META_DATA_SERVER_HXX
+
+#include "tag/TagType.h"
+
+struct Tag;
+class Page;
+
+/**
+ * Free the return value with delete[].
+ */
+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 TagType *types);
+
+#endif
diff --git a/src/output/plugins/httpd/Page.cxx b/src/output/plugins/httpd/Page.cxx
new file mode 100644
index 000000000..e22134bbc
--- /dev/null
+++ b/src/output/plugins/httpd/Page.cxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Page.hxx"
+#include "util/Alloc.hxx"
+
+#include <new>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+Page *
+Page::Create(size_t size)
+{
+ void *p = xalloc(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();
+ free(this);
+ }
+
+ return unused;
+}
diff --git a/src/output/plugins/httpd/Page.hxx b/src/output/plugins/httpd/Page.hxx
new file mode 100644
index 000000000..95f35d06a
--- /dev/null
+++ b/src/output/plugins/httpd/Page.hxx
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This is a library which manages reference counted buffers.
+ */
+
+#ifndef MPD_PAGE_HXX
+#define MPD_PAGE_HXX
+
+#include "util/RefCount.hxx"
+
+#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/output/plugins/sles/AndroidSimpleBufferQueue.hxx b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
new file mode 100644
index 000000000..c7dd4ccca
--- /dev/null
+++ b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP
+#define SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP
+
+#include <SLES/OpenSLES_Android.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLAndroidSimpleBufferQueueItf
+ * variable.
+ */
+ class AndroidSimpleBufferQueue {
+ SLAndroidSimpleBufferQueueItf queue;
+
+ public:
+ AndroidSimpleBufferQueue() = default;
+ explicit AndroidSimpleBufferQueue(SLAndroidSimpleBufferQueueItf _queue)
+ :queue(_queue) {}
+
+ SLresult Enqueue(const void *pBuffer, SLuint32 size) {
+ return (*queue)->Enqueue(queue, pBuffer, size);
+ }
+
+ SLresult Clear() {
+ return (*queue)->Clear(queue);
+ }
+
+ SLresult GetState(SLAndroidSimpleBufferQueueState *pState) {
+ return (*queue)->GetState(queue, pState);
+ }
+
+ SLresult RegisterCallback(slAndroidSimpleBufferQueueCallback callback,
+ void *pContext) {
+ return (*queue)->RegisterCallback(queue, callback, pContext);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/Engine.hxx b/src/output/plugins/sles/Engine.hxx
new file mode 100644
index 000000000..7c6e3cf50
--- /dev/null
+++ b/src/output/plugins/sles/Engine.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_ENGINE_HPP
+#define SLES_ENGINE_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLEngineItf variable.
+ */
+ class Engine {
+ SLEngineItf engine;
+
+ public:
+ Engine() = default;
+ explicit Engine(SLEngineItf _engine):engine(_engine) {}
+
+ SLresult CreateAudioPlayer(SLObjectItf *pPlayer,
+ SLDataSource *pAudioSrc, SLDataSink *pAudioSnk,
+ SLuint32 numInterfaces,
+ const SLInterfaceID *pInterfaceIds,
+ const SLboolean *pInterfaceRequired) {
+ return (*engine)->CreateAudioPlayer(engine, pPlayer,
+ pAudioSrc, pAudioSnk,
+ numInterfaces, pInterfaceIds,
+ pInterfaceRequired);
+ }
+
+ SLresult CreateOutputMix(SLObjectItf *pMix,
+ SLuint32 numInterfaces,
+ const SLInterfaceID *pInterfaceIds,
+ const SLboolean *pInterfaceRequired) {
+ return (*engine)->CreateOutputMix(engine, pMix,
+ numInterfaces, pInterfaceIds,
+ pInterfaceRequired);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/Object.hxx b/src/output/plugins/sles/Object.hxx
new file mode 100644
index 000000000..852d62d0d
--- /dev/null
+++ b/src/output/plugins/sles/Object.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_OBJECT_HPP
+#define SLES_OBJECT_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLObjectItf variable.
+ */
+ class Object {
+ SLObjectItf object;
+
+ public:
+ Object() = default;
+ explicit Object(SLObjectItf _object):object(_object) {}
+
+ operator SLObjectItf() {
+ return object;
+ }
+
+ SLresult Realize(bool async) {
+ return (*object)->Realize(object, async);
+ }
+
+ void Destroy() {
+ (*object)->Destroy(object);
+ }
+
+ SLresult GetInterface(const SLInterfaceID iid, void *pInterface) {
+ return (*object)->GetInterface(object, iid, pInterface);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/Play.hxx b/src/output/plugins/sles/Play.hxx
new file mode 100644
index 000000000..c760151ef
--- /dev/null
+++ b/src/output/plugins/sles/Play.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_PLAY_HPP
+#define SLES_PLAY_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+ /**
+ * OO wrapper for an OpenSL/ES SLPlayItf variable.
+ */
+ class Play {
+ SLPlayItf play;
+
+ public:
+ Play() = default;
+ explicit Play(SLPlayItf _play):play(_play) {}
+
+ SLresult SetPlayState(SLuint32 state) {
+ return (*play)->SetPlayState(play, state);
+ }
+ };
+}
+
+#endif
diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx
new file mode 100644
index 000000000..85fd9f2f2
--- /dev/null
+++ b/src/output/plugins/sles/SlesOutputPlugin.cxx
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SlesOutputPlugin.hxx"
+#include "Object.hxx"
+#include "Engine.hxx"
+#include "Play.hxx"
+#include "AndroidSimpleBufferQueue.hxx"
+#include "../../OutputAPI.hxx"
+#include "util/Macros.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+class SlesOutput {
+ static constexpr unsigned N_BUFFERS = 3;
+ static constexpr size_t BUFFER_SIZE = 65536;
+
+ AudioOutput base;
+
+ SLES::Object engine_object, mix_object, play_object;
+ SLES::Play play;
+ SLES::AndroidSimpleBufferQueue queue;
+
+ /**
+ * This mutex protects the attributes "next" and "filled". It
+ * is only needed while playback is launched, when the initial
+ * buffers are being enqueued in the caller thread, while
+ * another thread may invoke the registered callback.
+ */
+ Mutex mutex;
+
+ Cond cond;
+
+ bool pause, cancel;
+
+ /**
+ * The number of buffers queued to OpenSLES.
+ */
+ unsigned n_queued;
+
+ /**
+ * The index of the next buffer to be enqueued.
+ */
+ unsigned next;
+
+ /**
+ * Does the "next" buffer already contain synthesised samples?
+ * This can happen when PCMSynthesiser::Synthesise() has been
+ * called, but the OpenSL/ES buffer queue was full. The
+ * buffer will then be postponed.
+ */
+ unsigned filled;
+
+ /**
+ * An array of buffers. It's one more than being managed by
+ * OpenSL/ES, and the one not enqueued (see attribute #next)
+ * will be written to.
+ */
+ uint8_t buffers[N_BUFFERS][BUFFER_SIZE];
+
+public:
+ SlesOutput()
+ :base(sles_output_plugin) {}
+
+ operator AudioOutput *() {
+ return &base;
+ }
+
+ bool Initialize(const config_param &param, Error &error) {
+ return base.Configure(param, error);
+ }
+
+ bool Configure(const config_param &param, Error &error);
+
+ bool Open(AudioFormat &audio_format, Error &error);
+ void Close();
+
+ unsigned Delay() {
+ return pause && !cancel ? 100 : 0;
+ }
+
+ size_t Play(const void *chunk, size_t size, Error &error);
+
+ void Drain();
+ void Cancel();
+ bool Pause();
+
+private:
+ void PlayedCallback();
+
+ /**
+ * OpenSL/ES callback which gets invoked when a buffer has
+ * been consumed. It synthesises and enqueues the next
+ * buffer.
+ */
+ static void PlayedCallback(gcc_unused SLAndroidSimpleBufferQueueItf caller,
+ void *pContext)
+ {
+ SlesOutput &sles = *(SlesOutput *)pContext;
+ sles.PlayedCallback();
+ }
+};
+
+static constexpr Domain sles_domain("sles");
+
+inline bool
+SlesOutput::Configure(const config_param &, Error &)
+{
+ return true;
+}
+
+inline bool
+SlesOutput::Open(AudioFormat &audio_format, Error &error)
+{
+ SLresult result;
+ SLObjectItf _object;
+
+ result = slCreateEngine(&_object, 0, nullptr, 0,
+ nullptr, nullptr);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result), "slCreateEngine() failed");
+ return false;
+ }
+
+ engine_object = SLES::Object(_object);
+
+ result = engine_object.Realize(false);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result), "Engine.Realize() failed");
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLEngineItf _engine;
+ result = engine_object.GetInterface(SL_IID_ENGINE, &_engine);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Engine.GetInterface(IID_ENGINE) failed");
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLES::Engine engine(_engine);
+
+ result = engine.CreateOutputMix(&_object, 0, nullptr, nullptr);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Engine.CreateOutputMix() failed");
+ engine_object.Destroy();
+ return false;
+ }
+
+ mix_object = SLES::Object(_object);
+
+ result = mix_object.Realize(false);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Mix.Realize() failed");
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ N_BUFFERS,
+ };
+
+ if (audio_format.channels > 2)
+ audio_format.channels = 1;
+
+ SLDataFormat_PCM format_pcm;
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = audio_format.channels;
+ /* from the Android NDK docs: "Note that the field samplesPerSec is
+ actually in units of milliHz, despite the misleading name." */
+ format_pcm.samplesPerSec = audio_format.sample_rate * 1000u;
+ format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+ format_pcm.channelMask = audio_format.channels == 1
+ ? SL_SPEAKER_FRONT_CENTER
+ : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ format_pcm.endianness = IsLittleEndian()
+ ? SL_BYTEORDER_LITTLEENDIAN
+ : SL_BYTEORDER_BIGENDIAN;
+
+ SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+
+ SLDataLocator_OutputMix loc_outmix = {
+ SL_DATALOCATOR_OUTPUTMIX,
+ mix_object,
+ };
+
+ SLDataSink audioSnk = {
+ &loc_outmix,
+ nullptr,
+ };
+
+ const SLInterfaceID ids2[] = {
+ SL_IID_PLAY,
+ SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ SL_IID_ANDROIDCONFIGURATION,
+ };
+
+ static constexpr SLboolean req2[] = {
+ SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE,
+ };
+
+ result = engine.CreateAudioPlayer(&_object, &audioSrc, &audioSnk,
+ ARRAY_SIZE(ids2), ids2, req2);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Engine.CreateAudioPlayer() failed");
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ play_object = SLES::Object(_object);
+
+ SLAndroidConfigurationItf android_config;
+ if (play_object.GetInterface(SL_IID_ANDROIDCONFIGURATION,
+ &android_config) == SL_RESULT_SUCCESS) {
+ SLint32 stream_type = SL_ANDROID_STREAM_MEDIA;
+ (*android_config)->SetConfiguration(android_config,
+ SL_ANDROID_KEY_STREAM_TYPE,
+ &stream_type,
+ sizeof(stream_type));
+ }
+
+ result = play_object.Realize(false);
+
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.Realize() failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ SLPlayItf _play;
+ result = play_object.GetInterface(SL_IID_PLAY, &_play);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.GetInterface(IID_PLAY) failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ play = SLES::Play(_play);
+
+ SLAndroidSimpleBufferQueueItf _queue;
+ result = play_object.GetInterface(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &_queue);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.GetInterface(IID_ANDROIDSIMPLEBUFFERQUEUE) failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ queue = SLES::AndroidSimpleBufferQueue(_queue);
+ result = queue.RegisterCallback(PlayedCallback, (void *)this);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.RegisterCallback() failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ result = play.SetPlayState(SL_PLAYSTATE_PLAYING);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.SetPlayState(PLAYING) failed");
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+ return false;
+ }
+
+ pause = cancel = false;
+ n_queued = 0;
+ next = 0;
+ filled = 0;
+
+ // TODO: support other sample formats
+ audio_format.format = SampleFormat::S16;
+
+ return true;
+}
+
+inline void
+SlesOutput::Close()
+{
+ play.SetPlayState(SL_PLAYSTATE_STOPPED);
+ play_object.Destroy();
+ mix_object.Destroy();
+ engine_object.Destroy();
+}
+
+inline size_t
+SlesOutput::Play(const void *chunk, size_t size, Error &error)
+{
+ cancel = false;
+
+ if (pause) {
+ SLresult result = play.SetPlayState(SL_PLAYSTATE_PLAYING);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "Play.SetPlayState(PLAYING) failed");
+ return false;
+ }
+
+ pause = false;
+ }
+
+ const ScopeLock protect(mutex);
+
+ assert(filled < BUFFER_SIZE);
+
+ while (n_queued == N_BUFFERS) {
+ assert(filled == 0);
+ cond.wait(mutex);
+ }
+
+ size_t nbytes = std::min(BUFFER_SIZE - filled, size);
+ memcpy(buffers[next] + filled, chunk, nbytes);
+ filled += nbytes;
+ if (filled < BUFFER_SIZE)
+ return nbytes;
+
+ SLresult result = queue.Enqueue(buffers[next], BUFFER_SIZE);
+ if (result != SL_RESULT_SUCCESS) {
+ error.Set(sles_domain, int(result),
+ "AndroidSimpleBufferQueue.Enqueue() failed");
+ return 0;
+ }
+
+ ++n_queued;
+ next = (next + 1) % N_BUFFERS;
+ filled = 0;
+
+ return nbytes;
+}
+
+inline void
+SlesOutput::Drain()
+{
+ const ScopeLock protect(mutex);
+
+ assert(filled < BUFFER_SIZE);
+
+ while (n_queued > 0)
+ cond.wait(mutex);
+}
+
+inline void
+SlesOutput::Cancel()
+{
+ pause = true;
+ cancel = true;
+
+ SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED);
+ if (result != SL_RESULT_SUCCESS)
+ FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed");
+
+ result = queue.Clear();
+ if (result != SL_RESULT_SUCCESS)
+ FormatWarning(sles_domain,
+ "AndroidSimpleBufferQueue.Clear() failed");
+
+ const ScopeLock protect(mutex);
+ n_queued = 0;
+ filled = 0;
+}
+
+inline bool
+SlesOutput::Pause()
+{
+ cancel = false;
+
+ if (pause)
+ return true;
+
+ pause = true;
+
+ SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED);
+ if (result != SL_RESULT_SUCCESS) {
+ FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed");
+ return false;
+ }
+
+ return true;
+}
+
+inline void
+SlesOutput::PlayedCallback()
+{
+ const ScopeLock protect(mutex);
+ assert(n_queued > 0);
+ --n_queued;
+ cond.signal();
+}
+
+static bool
+sles_test_default_device()
+{
+ /* this is the default output plugin on Android, and it should
+ be available in any case */
+ return true;
+}
+
+static AudioOutput *
+sles_output_init(const config_param &param, Error &error)
+{
+ SlesOutput *sles = new SlesOutput();
+
+ if (!sles->Initialize(param, error) ||
+ !sles->Configure(param, error)) {
+ delete sles;
+ return nullptr;
+ }
+
+ return *sles;
+}
+
+static void
+sles_output_finish(AudioOutput *ao)
+{
+ SlesOutput *sles = (SlesOutput *)ao;
+
+ delete sles;
+}
+
+static bool
+sles_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Open(audio_format, error);
+}
+
+static void
+sles_output_close(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ sles.Close();
+}
+
+static unsigned
+sles_output_delay(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Delay();
+}
+
+static size_t
+sles_output_play(AudioOutput *ao, const void *chunk, size_t size,
+ Error &error)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Play(chunk, size, error);
+}
+
+static void
+sles_output_drain(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ sles.Drain();
+}
+
+static void
+sles_output_cancel(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ sles.Cancel();
+}
+
+static bool
+sles_output_pause(AudioOutput *ao)
+{
+ SlesOutput &sles = *(SlesOutput *)ao;
+
+ return sles.Pause();
+}
+
+const struct AudioOutputPlugin sles_output_plugin = {
+ "sles",
+ sles_test_default_device,
+ sles_output_init,
+ sles_output_finish,
+ nullptr,
+ nullptr,
+ sles_output_open,
+ sles_output_close,
+ sles_output_delay,
+ nullptr,
+ sles_output_play,
+ sles_output_drain,
+ sles_output_cancel,
+ sles_output_pause,
+ nullptr,
+};
diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx
new file mode 100644
index 000000000..5424dec2e
--- /dev/null
+++ b/src/output/plugins/sles/SlesOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SLES_OUTPUT_PLUGIN_HXX
+#define MPD_SLES_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin sles_output_plugin;
+
+#endif
diff --git a/src/pcm/ChannelsConverter.cxx b/src/pcm/ChannelsConverter.cxx
new file mode 100644
index 000000000..714613788
--- /dev/null
+++ b/src/pcm/ChannelsConverter.cxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ChannelsConverter.hxx"
+#include "PcmChannels.hxx"
+#include "Domain.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+bool
+PcmChannelsConverter::Open(SampleFormat _format,
+ unsigned _src_channels, unsigned _dest_channels,
+ gcc_unused Error &error)
+{
+ assert(_format != SampleFormat::UNDEFINED);
+
+ switch (_format) {
+ case SampleFormat::S16:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ break;
+
+ default:
+ error.Format(pcm_domain,
+ "PCM channel conversion for %s is not implemented",
+ sample_format_to_string(_format));
+ return false;
+ }
+
+ format = _format;
+ src_channels = _src_channels;
+ dest_channels = _dest_channels;
+ return true;
+}
+
+void
+PcmChannelsConverter::Close()
+{
+#ifndef NDEBUG
+ format = SampleFormat::UNDEFINED;
+#endif
+}
+
+ConstBuffer<void>
+PcmChannelsConverter::Convert(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S16:
+ return pcm_convert_channels_16(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<int16_t>::FromVoid(src)).ToVoid();
+
+ case SampleFormat::S24_P32:
+ return pcm_convert_channels_24(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<int32_t>::FromVoid(src)).ToVoid();
+
+ case SampleFormat::S32:
+ return pcm_convert_channels_32(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<int32_t>::FromVoid(src)).ToVoid();
+
+ case SampleFormat::FLOAT:
+ return pcm_convert_channels_float(buffer, dest_channels,
+ src_channels,
+ ConstBuffer<float>::FromVoid(src)).ToVoid();
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/ChannelsConverter.hxx b/src/pcm/ChannelsConverter.hxx
new file mode 100644
index 000000000..1374f9f5d
--- /dev/null
+++ b/src/pcm/ChannelsConverter.hxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_CHANNELS_CONVERTER_HXX
+#define MPD_PCM_CHANNELS_CONVERTER_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+#include "PcmBuffer.hxx"
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+class Error;
+template<typename T> struct ConstBuffer;
+
+/**
+ * A class that converts samples from one format to another.
+ */
+class PcmChannelsConverter {
+ SampleFormat format;
+ unsigned src_channels, dest_channels;
+
+ PcmBuffer buffer;
+
+public:
+#ifndef NDEBUG
+ PcmChannelsConverter()
+ :format(SampleFormat::UNDEFINED) {}
+
+ ~PcmChannelsConverter() {
+ assert(format == SampleFormat::UNDEFINED);
+ }
+#endif
+
+ /**
+ * Opens the object, prepare for Convert().
+ *
+ * @param format the sample format
+ * @param src_channels the number of source channels
+ * @param dest_channels the number of destination channels
+ * @param error location to store the error
+ * @return true on success
+ */
+ bool Open(SampleFormat format,
+ unsigned src_channels, unsigned dest_channels,
+ Error &error);
+
+ /**
+ * Closes the object. After that, you may call Open() again.
+ */
+ void Close();
+
+ /**
+ * Convert a block of PCM data.
+ *
+ * @param src the input buffer
+ * @param error location to store the error
+ * @return the destination buffer on success,
+ * ConstBuffer::Null() on error
+ */
+ gcc_pure
+ ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error);
+};
+
+#endif
diff --git a/src/pcm/ConfiguredResampler.cxx b/src/pcm/ConfiguredResampler.cxx
new file mode 100644
index 000000000..f6aec3f95
--- /dev/null
+++ b/src/pcm/ConfiguredResampler.cxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConfiguredResampler.hxx"
+#include "FallbackResampler.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "config/ConfigError.hxx"
+#include "util/Error.hxx"
+
+#ifdef HAVE_LIBSAMPLERATE
+#include "LibsamplerateResampler.hxx"
+#endif
+
+#ifdef HAVE_SOXR
+#include "SoxrResampler.hxx"
+#endif
+
+#include <string.h>
+
+enum class SelectedResampler {
+ FALLBACK,
+
+#ifdef HAVE_LIBSAMPLERATE
+ LIBSAMPLERATE,
+#endif
+
+#ifdef HAVE_SOXR
+ SOXR,
+#endif
+};
+
+static SelectedResampler selected_resampler = SelectedResampler::FALLBACK;
+
+bool
+pcm_resampler_global_init(Error &error)
+{
+ const char *converter =
+ config_get_string(CONF_SAMPLERATE_CONVERTER, "");
+
+ if (strcmp(converter, "internal") == 0)
+ return true;
+
+#ifdef HAVE_SOXR
+ if (memcmp(converter, "soxr", 4) == 0) {
+ selected_resampler = SelectedResampler::SOXR;
+ return pcm_resample_soxr_global_init(converter, error);
+ }
+#endif
+
+#ifdef HAVE_LIBSAMPLERATE
+ selected_resampler = SelectedResampler::LIBSAMPLERATE;
+ return pcm_resample_lsr_global_init(converter, error);
+#endif
+
+ if (*converter == 0)
+ return true;
+
+ error.Format(config_domain,
+ "The samplerate_converter '%s' is not available",
+ converter);
+ return false;
+}
+
+PcmResampler *
+pcm_resampler_create()
+{
+ switch (selected_resampler) {
+ case SelectedResampler::FALLBACK:
+ return new FallbackPcmResampler();
+
+#ifdef HAVE_LIBSAMPLERATE
+ case SelectedResampler::LIBSAMPLERATE:
+ return new LibsampleratePcmResampler();
+#endif
+
+#ifdef HAVE_SOXR
+ case SelectedResampler::SOXR:
+ return new SoxrPcmResampler();
+#endif
+ }
+
+ gcc_unreachable();
+}
diff --git a/src/pcm/ConfiguredResampler.hxx b/src/pcm/ConfiguredResampler.hxx
new file mode 100644
index 000000000..2b14b381e
--- /dev/null
+++ b/src/pcm/ConfiguredResampler.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONFIGURED_RESAMPLER_HXX
+#define MPD_CONFIGURED_RESAMPLER_HXX
+
+#include "check.h"
+
+class Error;
+class PcmResampler;
+
+bool
+pcm_resampler_global_init(Error &error);
+
+/**
+ * Create a #PcmResampler instance from the implementation class
+ * configured in mpd.conf.
+ */
+PcmResampler *
+pcm_resampler_create();
+
+#endif
diff --git a/src/pcm/Domain.cxx b/src/pcm/Domain.cxx
new file mode 100644
index 000000000..ecd5c22a4
--- /dev/null
+++ b/src/pcm/Domain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Domain.hxx"
+#include "util/Domain.hxx"
+
+const Domain pcm_domain("pcm");
diff --git a/src/pcm/Domain.hxx b/src/pcm/Domain.hxx
new file mode 100644
index 000000000..781d5c71b
--- /dev/null
+++ b/src/pcm/Domain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PCM_DOMAIN_HXX
+#define PCM_DOMAIN_HXX
+
+class Domain;
+
+extern const Domain pcm_domain;
+
+#endif
diff --git a/src/pcm/FallbackResampler.cxx b/src/pcm/FallbackResampler.cxx
new file mode 100644
index 000000000..bd3f20d86
--- /dev/null
+++ b/src/pcm/FallbackResampler.cxx
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FallbackResampler.hxx"
+
+#include <assert.h>
+
+AudioFormat
+FallbackPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate,
+ gcc_unused Error &error)
+{
+ assert(af.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ switch (af.format) {
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ af.format = SampleFormat::S16;
+ break;
+
+ case SampleFormat::S16:
+ case SampleFormat::FLOAT:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ break;
+
+ case SampleFormat::DSD:
+ af.format = SampleFormat::FLOAT;
+ break;
+ }
+
+ format = af;
+ out_rate = new_sample_rate;
+
+ AudioFormat result = af;
+ result.sample_rate = new_sample_rate;
+ return result;
+}
+
+void
+FallbackPcmResampler::Close()
+{
+}
+
+template<typename T>
+static ConstBuffer<T>
+pcm_resample_fallback(PcmBuffer &buffer,
+ unsigned channels,
+ unsigned src_rate,
+ ConstBuffer<T> src,
+ unsigned dest_rate)
+{
+ unsigned dest_pos = 0;
+ unsigned src_frames = src.size / channels;
+ unsigned dest_frames =
+ (src_frames * dest_rate + src_rate - 1) / src_rate;
+ unsigned dest_samples = dest_frames * channels;
+ size_t dest_size = dest_samples * sizeof(*src.data);
+ T *dest_buffer = (T *)buffer.Get(dest_size);
+
+ assert((src.size % channels) == 0);
+
+ switch (channels) {
+ case 1:
+ while (dest_pos < dest_samples) {
+ unsigned src_pos = dest_pos * src_rate / dest_rate;
+
+ dest_buffer[dest_pos++] = src[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[src_pos];
+ dest_buffer[dest_pos++] = src[src_pos + 1];
+ }
+ break;
+ }
+
+ return { dest_buffer, dest_samples };
+}
+
+template<typename T>
+static ConstBuffer<void>
+pcm_resample_fallback_void(PcmBuffer &buffer,
+ unsigned channels,
+ unsigned src_rate,
+ ConstBuffer<void> src,
+ unsigned dest_rate)
+{
+ const auto typed_src = ConstBuffer<T>::FromVoid(src);
+ return pcm_resample_fallback(buffer, channels, src_rate, typed_src,
+ dest_rate).ToVoid();
+}
+
+ConstBuffer<void>
+FallbackPcmResampler::Resample(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ switch (format.format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S16:
+ return pcm_resample_fallback_void<int16_t>(buffer,
+ format.channels,
+ format.sample_rate,
+ src,
+ out_rate);
+
+ case SampleFormat::FLOAT:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ return pcm_resample_fallback_void<int32_t>(buffer,
+ format.channels,
+ format.sample_rate,
+ src,
+ out_rate);
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/FallbackResampler.hxx b/src/pcm/FallbackResampler.hxx
new file mode 100644
index 000000000..38273f53f
--- /dev/null
+++ b/src/pcm/FallbackResampler.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_FALLBACK_RESAMPLER_HXX
+#define MPD_PCM_FALLBACK_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "Compiler.h"
+
+/**
+ * A naive resampler that is used when no external library was found
+ * (or when the user explicitly asks for bad quality).
+ */
+class FallbackPcmResampler final : public PcmResampler {
+ AudioFormat format;
+ unsigned out_rate;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+#endif
diff --git a/src/pcm/FloatConvert.hxx b/src/pcm/FloatConvert.hxx
new file mode 100644
index 000000000..93e867159
--- /dev/null
+++ b/src/pcm/FloatConvert.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_FLOAT_CONVERT_HXX
+#define MPD_PCM_FLOAT_CONVERT_HXX
+
+#include "Traits.hxx"
+
+/**
+ * Convert from float to an integer sample format.
+ */
+template<SampleFormat F, class Traits=SampleTraits<F>>
+struct FloatToIntegerSampleConvert {
+ typedef SampleTraits<SampleFormat::FLOAT> SrcTraits;
+ typedef Traits DstTraits;
+
+ typedef typename SrcTraits::value_type SV;
+ typedef typename SrcTraits::long_type SL;
+ typedef typename DstTraits::value_type DV;
+
+ static constexpr SV factor = 1 << (DstTraits::BITS - 1);
+
+ gcc_const
+ static DV Convert(SV src) {
+ return PcmClamp<F, Traits>(SL(src * factor));
+ }
+};
+
+/**
+ * Convert from an integer sample format to float.
+ */
+template<SampleFormat F, class Traits=SampleTraits<F>>
+struct IntegerToFloatSampleConvert {
+ typedef SampleTraits<SampleFormat::FLOAT> DstTraits;
+ typedef Traits SrcTraits;
+
+ typedef typename SrcTraits::value_type SV;
+ typedef typename DstTraits::value_type DV;
+
+ static constexpr DV factor = 0.5 / (1 << (SrcTraits::BITS - 2));
+
+ gcc_const
+ static DV Convert(SV src) {
+ return DV(src) * factor;
+ }
+};
+
+#endif
diff --git a/src/pcm/FormatConverter.cxx b/src/pcm/FormatConverter.cxx
new file mode 100644
index 000000000..b058b32f5
--- /dev/null
+++ b/src/pcm/FormatConverter.cxx
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FormatConverter.hxx"
+#include "PcmFormat.hxx"
+#include "Domain.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+bool
+PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format,
+ Error &error)
+{
+ assert(_src_format != SampleFormat::UNDEFINED);
+ assert(_dest_format != SampleFormat::UNDEFINED);
+
+ switch (_dest_format) {
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ error.Format(pcm_domain,
+ "PCM conversion from %s to %s is not implemented",
+ sample_format_to_string(_src_format),
+ sample_format_to_string(_dest_format));
+ return nullptr;
+
+ case SampleFormat::S16:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ break;
+ }
+
+ src_format = _src_format;
+ dest_format = _dest_format;
+ return true;
+}
+
+void
+PcmFormatConverter::Close()
+{
+#ifndef NDEBUG
+ src_format = SampleFormat::UNDEFINED;
+ dest_format = SampleFormat::UNDEFINED;
+#endif
+}
+
+ConstBuffer<void>
+PcmFormatConverter::Convert(ConstBuffer<void> src, gcc_unused Error &error)
+{
+ switch (dest_format) {
+ case SampleFormat::UNDEFINED:
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S16:
+ return pcm_convert_to_16(buffer, dither,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::S24_P32:
+ return pcm_convert_to_24(buffer,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::S32:
+ return pcm_convert_to_32(buffer,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::FLOAT:
+ return pcm_convert_to_float(buffer,
+ src_format,
+ src).ToVoid();
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/FormatConverter.hxx b/src/pcm/FormatConverter.hxx
new file mode 100644
index 000000000..3d8b6fb75
--- /dev/null
+++ b/src/pcm/FormatConverter.hxx
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_FORMAT_CONVERTER_HXX
+#define MPD_PCM_FORMAT_CONVERTER_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+#include "PcmBuffer.hxx"
+#include "PcmDither.hxx"
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+class Error;
+template<typename T> struct ConstBuffer;
+
+/**
+ * A class that converts samples from one format to another.
+ */
+class PcmFormatConverter {
+ SampleFormat src_format, dest_format;
+
+ PcmBuffer buffer;
+ PcmDither dither;
+
+public:
+#ifndef NDEBUG
+ PcmFormatConverter()
+ :src_format(SampleFormat::UNDEFINED),
+ dest_format(SampleFormat::UNDEFINED) {}
+
+ ~PcmFormatConverter() {
+ assert(src_format == SampleFormat::UNDEFINED);
+ assert(dest_format == SampleFormat::UNDEFINED);
+ }
+#endif
+
+ /**
+ * Opens the object, prepare for Convert().
+ *
+ * @param src_format the sample format of incoming data
+ * @param dest_format the sample format of outgoing data
+ * @param error location to store the error
+ * @return true on success
+ */
+ bool Open(SampleFormat src_format, SampleFormat dest_format,
+ Error &error);
+
+ /**
+ * Closes the object. After that, you may call Open() again.
+ */
+ void Close();
+
+ /**
+ * Convert a block of PCM data.
+ *
+ * @param src the input buffer
+ * @param error location to store the error
+ * @return the destination buffer on success,
+ * ConstBuffer::Null() on error
+ */
+ gcc_pure
+ ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error);
+};
+
+#endif
diff --git a/src/pcm/GlueResampler.cxx b/src/pcm/GlueResampler.cxx
new file mode 100644
index 000000000..0f5fe0271
--- /dev/null
+++ b/src/pcm/GlueResampler.cxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "GlueResampler.hxx"
+#include "ConfiguredResampler.hxx"
+#include "Resampler.hxx"
+
+#include <assert.h>
+
+GluePcmResampler::GluePcmResampler()
+ :resampler(pcm_resampler_create()) {}
+
+GluePcmResampler::~GluePcmResampler()
+{
+ delete resampler;
+}
+
+bool
+GluePcmResampler::Open(AudioFormat src_format, unsigned new_sample_rate,
+ Error &error)
+{
+ assert(src_format.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ AudioFormat requested_format = src_format;
+ AudioFormat dest_format = resampler->Open(requested_format,
+ new_sample_rate,
+ error);
+ if (!dest_format.IsValid())
+ return false;
+
+ assert(requested_format.channels == src_format.channels);
+ assert(dest_format.channels == src_format.channels);
+ assert(dest_format.sample_rate == new_sample_rate);
+
+ if (requested_format.format != src_format.format &&
+ !format_converter.Open(src_format.format, requested_format.format,
+ error))
+ return false;
+
+ src_sample_format = src_format.format;
+ requested_sample_format = requested_format.format;
+ output_sample_format = dest_format.format;
+ return true;
+}
+
+void
+GluePcmResampler::Close()
+{
+ if (requested_sample_format != src_sample_format)
+ format_converter.Close();
+
+ resampler->Close();
+}
+
+ConstBuffer<void>
+GluePcmResampler::Resample(ConstBuffer<void> src, Error &error)
+{
+ assert(!src.IsNull());
+
+ if (requested_sample_format != src_sample_format) {
+ src = format_converter.Convert(src, error);
+ if (src.IsNull())
+ return nullptr;
+ }
+
+ return resampler->Resample(src, error);
+}
diff --git a/src/pcm/GlueResampler.hxx b/src/pcm/GlueResampler.hxx
new file mode 100644
index 000000000..aff07823e
--- /dev/null
+++ b/src/pcm/GlueResampler.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_GLUE_RESAMPLER_HXX
+#define MPD_GLUE_RESAMPLER_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+#include "FormatConverter.hxx"
+
+class Error;
+class PcmResampler;
+template<typename T> struct ConstBuffer;
+
+/**
+ * A glue class that integrates a #PcmResampler and automatically
+ * converts source data to the sample format required by the
+ * #PcmResampler instance.
+ */
+class GluePcmResampler {
+ PcmResampler *const resampler;
+
+ SampleFormat src_sample_format, requested_sample_format;
+ SampleFormat output_sample_format;
+
+ /**
+ * This object converts input data to the sample format
+ * requested by the #PcmResampler.
+ */
+ PcmFormatConverter format_converter;
+
+public:
+ GluePcmResampler();
+ ~GluePcmResampler();
+
+ bool Open(AudioFormat src_format, unsigned new_sample_rate,
+ Error &error);
+ void Close();
+
+ SampleFormat GetOutputSampleFormat() const {
+ return output_sample_format;
+ }
+
+ ConstBuffer<void> Resample(ConstBuffer<void> src, Error &error);
+};
+
+#endif
diff --git a/src/pcm/LibsamplerateResampler.cxx b/src/pcm/LibsamplerateResampler.cxx
new file mode 100644
index 000000000..8b22f1e32
--- /dev/null
+++ b/src/pcm/LibsamplerateResampler.cxx
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LibsamplerateResampler.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+static constexpr Domain libsamplerate_domain("libsamplerate");
+
+static int lsr_converter = SRC_SINC_FASTEST;
+
+static bool
+lsr_parse_converter(const char *s)
+{
+ assert(s != nullptr);
+
+ if (*s == 0)
+ return true;
+
+ char *endptr;
+ long l = strtol(s, &endptr, 10);
+ if (*endptr == 0 && src_get_name(l) != nullptr) {
+ lsr_converter = l;
+ return true;
+ }
+
+ size_t length = strlen(s);
+ for (int i = 0;; ++i) {
+ const char *name = src_get_name(i);
+ if (name == nullptr)
+ break;
+
+ if (StringEqualsCaseASCII(s, name, length)) {
+ lsr_converter = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error)
+{
+ if (!lsr_parse_converter(converter)) {
+ error.Format(libsamplerate_domain,
+ "unknown samplerate converter '%s'", converter);
+ return false;
+ }
+
+ FormatDebug(libsamplerate_domain,
+ "libsamplerate converter '%s'",
+ src_get_name(lsr_converter));
+
+ return true;
+}
+
+AudioFormat
+LibsampleratePcmResampler::Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error)
+{
+ assert(af.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ src_rate = af.sample_rate;
+ dest_rate = new_sample_rate;
+ channels = af.channels;
+
+ /* libsamplerate works with floating point samples */
+ af.format = SampleFormat::FLOAT;
+
+ int src_error;
+ state = src_new(lsr_converter, channels, &src_error);
+ if (!state) {
+ error.Format(libsamplerate_domain, src_error,
+ "libsamplerate initialization has failed: %s",
+ src_strerror(src_error));
+ return AudioFormat::Undefined();
+ }
+
+ memset(&data, 0, sizeof(data));
+
+ data.src_ratio = double(new_sample_rate) / double(af.sample_rate);
+ FormatDebug(libsamplerate_domain,
+ "setting samplerate conversion ratio to %.2lf",
+ data.src_ratio);
+ src_set_ratio(state, data.src_ratio);
+
+ AudioFormat result = af;
+ result.sample_rate = new_sample_rate;
+ return result;
+}
+
+void
+LibsampleratePcmResampler::Close()
+{
+ state = src_delete(state);
+}
+
+static bool
+src_process(SRC_STATE *state, SRC_DATA *data, Error &error)
+{
+ int result = src_process(state, data);
+ if (result != 0) {
+ error.Format(libsamplerate_domain, result,
+ "libsamplerate has failed: %s",
+ src_strerror(result));
+ return false;
+ }
+
+ return true;
+}
+
+inline ConstBuffer<float>
+LibsampleratePcmResampler::Resample2(ConstBuffer<float> src, Error &error)
+{
+ assert(src.size % channels == 0);
+
+ const unsigned src_frames = src.size / channels;
+ const unsigned dest_frames =
+ (src_frames * dest_rate + src_rate - 1) / src_rate;
+ size_t data_out_size = dest_frames * sizeof(float) * channels;
+
+ data.data_in = const_cast<float *>(src.data);
+ data.data_out = (float *)buffer.Get(data_out_size);
+ data.input_frames = src_frames;
+ data.output_frames = dest_frames;
+
+ if (!src_process(state, &data, error))
+ return nullptr;
+
+ return ConstBuffer<float>(data.data_out,
+ data.output_frames_gen * channels);
+}
+
+ConstBuffer<void>
+LibsampleratePcmResampler::Resample(ConstBuffer<void> src, Error &error)
+{
+ return Resample2(ConstBuffer<float>::FromVoid(src), error).ToVoid();
+}
diff --git a/src/pcm/LibsamplerateResampler.hxx b/src/pcm/LibsamplerateResampler.hxx
new file mode 100644
index 000000000..4f4e645e6
--- /dev/null
+++ b/src/pcm/LibsamplerateResampler.hxx
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX
+#define MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "Compiler.h"
+
+#include <samplerate.h>
+
+/**
+ * A resampler using libsamplerate.
+ */
+class LibsampleratePcmResampler final : public PcmResampler {
+ unsigned src_rate, dest_rate;
+ unsigned channels;
+
+ SRC_STATE *state;
+ SRC_DATA data;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+
+private:
+ ConstBuffer<float> Resample2(ConstBuffer<float> src, Error &error);
+};
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error);
+
+#endif
diff --git a/src/pcm/Neon.hxx b/src/pcm/Neon.hxx
new file mode 100644
index 000000000..7109778ab
--- /dev/null
+++ b/src/pcm/Neon.hxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_NEON_HXX
+#define MPD_PCM_NEON_HXX
+
+#include "Traits.hxx"
+
+#include <arm_neon.h>
+
+/**
+ * Call a NEON intrinsic for each element in the vector.
+ *
+ * @param func the NEON intrinsic
+ * @param result the vector variable that gets assigned the result
+ * @param vector the input vector
+ */
+#define neon_x4_u(func, result, vector) do { \
+ result.val[0] = func(vector.val[0]); \
+ result.val[1] = func(vector.val[1]); \
+ result.val[2] = func(vector.val[2]); \
+ result.val[3] = func(vector.val[3]); \
+} while (0)
+
+/**
+ * Call a NEON intrinsic for each element in the vector.
+ *
+ * @param func the NEON intrinsic
+ * @param result the vector variable that gets assigned the result
+ * @param vector the input vector
+ */
+#define neon_x4_b(func, result, vector, ...) do { \
+ result.val[0] = func(vector.val[0], __VA_ARGS__); \
+ result.val[1] = func(vector.val[1], __VA_ARGS__); \
+ result.val[2] = func(vector.val[2], __VA_ARGS__); \
+ result.val[3] = func(vector.val[3], __VA_ARGS__); \
+} while (0)
+
+/**
+ * Convert floating point samples to 16 bit signed integer using ARM NEON.
+ */
+struct NeonFloatTo16 {
+ static constexpr SampleFormat src_format = SampleFormat::FLOAT;
+ static constexpr SampleFormat dst_format = SampleFormat::S16;
+ typedef SampleTraits<src_format> SrcTraits;
+ typedef SampleTraits<dst_format> DstTraits;
+
+ typedef typename SrcTraits::value_type SV;
+ typedef typename DstTraits::value_type DV;
+
+ static constexpr size_t BLOCK_SIZE = 16;
+
+ void Convert(int16_t *dst, const float *src, const size_t n) const {
+ for (unsigned i = 0; i < n / BLOCK_SIZE;
+ ++i, src += BLOCK_SIZE, dst += BLOCK_SIZE) {
+ /* load 16 float samples into 4 quad
+ registers */
+ float32x4x4_t value = vld4q_f32(src);
+
+ /* convert to 32 bit integer */
+ int32x4x4_t ivalue;
+ neon_x4_b(vcvtq_n_s32_f32, ivalue, value,
+ 30);
+
+ /* convert to 16 bit integer with saturation
+ and rounding */
+ int16x4x4_t nvalue;
+ neon_x4_b(vqrshrn_n_s32, nvalue, ivalue,
+ 30 - DstTraits::BITS + 1);
+
+ /* store result */
+ vst4_s16(dst, nvalue);
+ }
+ }
+};
+
+#endif
diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx
index 578c579be..7bba2de47 100644
--- a/src/pcm/PcmBuffer.cxx
+++ b/src/pcm/PcmBuffer.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,6 @@
#include "config.h"
#include "PcmBuffer.hxx"
-#include "poison.h"
void *
PcmBuffer::Get(size_t new_size)
diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx
index 717e24938..f56a85985 100644
--- a/src/pcm/PcmBuffer.hxx
+++ b/src/pcm/PcmBuffer.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -49,6 +49,12 @@ public:
*/
gcc_malloc
void *Get(size_t size);
+
+ template<typename T>
+ gcc_malloc
+ T *GetT(size_t n) {
+ return (T *)Get(n * sizeof(T));
+ }
};
#endif
diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx
index eb69985c1..276f31045 100644
--- a/src/pcm/PcmChannels.cxx
+++ b/src/pcm/PcmChannels.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,9 @@
#include "config.h"
#include "PcmChannels.hxx"
#include "PcmBuffer.hxx"
-#include "PcmUtils.hxx"
+#include "Traits.hxx"
+#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include <assert.h>
@@ -37,254 +39,143 @@ MonoToStereo(D dest, S src, S end)
}
-static void
-pcm_convert_channels_16_2_to_1(int16_t *gcc_restrict dest,
- const int16_t *gcc_restrict src,
- const int16_t *gcc_restrict src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::value_type
+StereoToMono(typename Traits::value_type _a,
+ typename Traits::value_type _b)
{
- while (src < src_end) {
- int32_t a = *src++, b = *src++;
+ typename Traits::sum_type a(_a);
+ typename Traits::sum_type b(_b);
- *dest++ = (a + b) / 2;
- }
+ return typename Traits::value_type((a + b) / 2);
}
-static void
-pcm_convert_channels_16_n_to_2(int16_t *gcc_restrict dest,
- unsigned src_channels,
- const int16_t *gcc_restrict src,
- const int16_t *gcc_restrict src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+StereoToMono(typename Traits::pointer_type dest,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
{
- unsigned c;
-
- assert(src_channels > 0);
-
- while (src < src_end) {
- int32_t sum = 0;
- int16_t value;
-
- for (c = 0; c < src_channels; ++c)
- sum += *src++;
- value = sum / (int)src_channels;
+ while (src != end) {
+ const auto a = *src++;
+ const auto b = *src++;
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
+ *dest++ = StereoToMono<F, Traits>(a, b);
}
-}
-
-const int16_t *
-pcm_convert_channels_16(PcmBuffer &buffer,
- unsigned dest_channels,
- unsigned src_channels, const int16_t *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
-
- int16_t *dest = (int16_t *)buffer.Get(dest_size);
- const int16_t *src_end = pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_16_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_16_n_to_2(dest, src_channels, src,
- src_end);
- else
- return nullptr;
return dest;
}
-static void
-pcm_convert_channels_24_2_to_1(int32_t *gcc_restrict dest,
- const int32_t *gcc_restrict src,
- const int32_t *gcc_restrict src_end)
-{
- while (src < src_end) {
- int32_t a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
-}
-
-static void
-pcm_convert_channels_24_n_to_2(int32_t *gcc_restrict dest,
- unsigned src_channels,
- const int32_t *gcc_restrict src,
- const int32_t *gcc_restrict src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+NToStereo(typename Traits::pointer_type dest,
+ unsigned src_channels,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
{
- unsigned c;
+ assert((end - src) % src_channels == 0);
- assert(src_channels > 0);
-
- while (src < src_end) {
- int32_t sum = 0;
- int32_t value;
-
- for (c = 0; c < src_channels; ++c)
+ while (src != end) {
+ typename Traits::sum_type sum = *src++;
+ for (unsigned c = 1; c < src_channels; ++c)
sum += *src++;
- value = sum / (int)src_channels;
- /* XXX this is actually only mono ... */
+ typename Traits::value_type value(sum / int(src_channels));
+
+ /* TODO: this is actually only mono ... */
*dest++ = value;
*dest++ = value;
}
-}
-
-const int32_t *
-pcm_convert_channels_24(PcmBuffer &buffer,
- unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r)
-{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
-
- int32_t *dest = (int32_t *)buffer.Get(dest_size);
- const int32_t *src_end = (const int32_t *)
- pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_24_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_24_n_to_2(dest, src_channels, src,
- src_end);
- else
- return nullptr;
return dest;
}
-static void
-pcm_convert_channels_32_2_to_1(int32_t *gcc_restrict dest,
- const int32_t *gcc_restrict src,
- const int32_t *gcc_restrict src_end)
-{
- while (src < src_end) {
- int64_t a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
-}
-
-static void
-pcm_convert_channels_32_n_to_2(int32_t *dest,
- unsigned src_channels, const int32_t *src,
- const int32_t *src_end)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::pointer_type
+NToM(typename Traits::pointer_type dest,
+ unsigned dest_channels,
+ unsigned src_channels,
+ typename Traits::const_pointer_type src,
+ typename Traits::const_pointer_type end)
{
- unsigned c;
+ assert((end - src) % src_channels == 0);
- assert(src_channels > 0);
-
- while (src < src_end) {
- int64_t sum = 0;
- int32_t value;
-
- for (c = 0; c < src_channels; ++c)
+ while (src != end) {
+ typename Traits::sum_type sum = *src++;
+ for (unsigned c = 1; c < src_channels; ++c)
sum += *src++;
- value = sum / (int64_t)src_channels;
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
+ typename Traits::value_type value(sum / int(src_channels));
+
+ /* TODO: this is actually only mono ... */
+ for (unsigned c = 0; c < dest_channels; ++c)
+ *dest++ = value;
}
+
+ return dest;
}
-const int32_t *
-pcm_convert_channels_32(PcmBuffer &buffer,
- unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static ConstBuffer<typename Traits::value_type>
+ConvertChannels(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<typename Traits::value_type> src)
{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
+ assert(src.size % src_channels == 0);
- int32_t *dest = (int32_t *)buffer.Get(dest_size);
- const int32_t *src_end = (const int32_t *)
- pcm_end_pointer(src, src_size);
+ const size_t dest_size = src.size / src_channels * dest_channels;
+ auto dest = buffer.GetT<typename Traits::value_type>(dest_size);
if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
+ MonoToStereo(dest, src.begin(), src.end());
else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_32_2_to_1(dest, src, src_end);
+ StereoToMono<F>(dest, src.begin(), src.end());
else if (dest_channels == 2)
- pcm_convert_channels_32_n_to_2(dest, src_channels, src,
- src_end);
+ NToStereo<F>(dest, src_channels, src.begin(), src.end());
else
- return nullptr;
+ NToM<F>(dest, dest_channels,
+ src_channels, src.begin(), src.end());
- return dest;
+ return { dest, dest_size };
}
-static void
-pcm_convert_channels_float_2_to_1(float *gcc_restrict dest,
- const float *gcc_restrict src,
- const float *gcc_restrict src_end)
+ConstBuffer<int16_t>
+pcm_convert_channels_16(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<int16_t> src)
{
- while (src < src_end) {
- double a = *src++, b = *src++;
-
- *dest++ = (a + b) / 2;
- }
+ return ConvertChannels<SampleFormat::S16>(buffer, dest_channels,
+ src_channels, src);
}
-static void
-pcm_convert_channels_float_n_to_2(float *dest,
- unsigned src_channels, const float *src,
- const float *src_end)
+ConstBuffer<int32_t>
+pcm_convert_channels_24(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<int32_t> src)
{
- unsigned c;
-
- assert(src_channels > 0);
-
- while (src < src_end) {
- double sum = 0;
- float value;
-
- for (c = 0; c < src_channels; ++c)
- sum += *src++;
- value = sum / (double)src_channels;
+ return ConvertChannels<SampleFormat::S24_P32>(buffer, dest_channels,
+ src_channels, src);
+}
- /* XXX this is actually only mono ... */
- *dest++ = value;
- *dest++ = value;
- }
+ConstBuffer<int32_t>
+pcm_convert_channels_32(PcmBuffer &buffer,
+ unsigned dest_channels,
+ unsigned src_channels,
+ ConstBuffer<int32_t> src)
+{
+ return ConvertChannels<SampleFormat::S32>(buffer, dest_channels,
+ src_channels, src);
}
-const float *
+ConstBuffer<float>
pcm_convert_channels_float(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const float *src,
- size_t src_size, size_t *dest_size_r)
+ unsigned src_channels,
+ ConstBuffer<float> src)
{
- assert(src_size % (sizeof(*src) * src_channels) == 0);
-
- size_t dest_size = src_size / src_channels * dest_channels;
- *dest_size_r = dest_size;
-
- float *dest = (float *)buffer.Get(dest_size);
- const float *src_end = (const float *)pcm_end_pointer(src, src_size);
-
- if (src_channels == 1 && dest_channels == 2)
- MonoToStereo(dest, src, src_end);
- else if (src_channels == 2 && dest_channels == 1)
- pcm_convert_channels_float_2_to_1(dest, src, src_end);
- else if (dest_channels == 2)
- pcm_convert_channels_float_n_to_2(dest, src_channels, src,
- src_end);
- else
- return nullptr;
-
- return dest;
+ return ConvertChannels<SampleFormat::FLOAT>(buffer, dest_channels,
+ src_channels, src);
}
diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx
index c67822825..6ad093c3b 100644
--- a/src/pcm/PcmChannels.hxx
+++ b/src/pcm/PcmChannels.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include <stddef.h>
class PcmBuffer;
+template<typename T> struct ConstBuffer;
/**
* Changes the number of channels in 16 bit PCM data.
@@ -32,15 +33,13 @@ class PcmBuffer;
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int16_t *
+ConstBuffer<int16_t>
pcm_convert_channels_16(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const int16_t *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<int16_t> src);
/**
* Changes the number of channels in 24 bit PCM data (aligned at 32
@@ -50,15 +49,13 @@ pcm_convert_channels_16(PcmBuffer &buffer,
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_channels_24(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<int32_t> src);
/**
* Changes the number of channels in 32 bit PCM data.
@@ -67,15 +64,13 @@ pcm_convert_channels_24(PcmBuffer &buffer,
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_channels_32(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const int32_t *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<int32_t> src);
/**
* Changes the number of channels in 32 bit float PCM data.
@@ -84,14 +79,12 @@ pcm_convert_channels_32(PcmBuffer &buffer,
* @param dest_channels the number of channels requested
* @param src_channels the number of channels in the source buffer
* @param src the source PCM buffer
- * @param src_size the number of bytes in #src
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const float *
+ConstBuffer<float>
pcm_convert_channels_float(PcmBuffer &buffer,
unsigned dest_channels,
- unsigned src_channels, const float *src,
- size_t src_size, size_t *dest_size_r);
+ unsigned src_channels,
+ ConstBuffer<float> src);
#endif
diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx
index 8eafe527c..438566759 100644
--- a/src/pcm/PcmConvert.cxx
+++ b/src/pcm/PcmConvert.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,289 +19,141 @@
#include "config.h"
#include "PcmConvert.hxx"
-#include "PcmChannels.hxx"
-#include "PcmFormat.hxx"
+#include "Domain.hxx"
+#include "ConfiguredResampler.hxx"
#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "util/ConstBuffer.hxx"
#include <assert.h>
#include <math.h>
-const Domain pcm_convert_domain("pcm_convert");
-
-PcmConvert::PcmConvert()
+bool
+pcm_convert_global_init(Error &error)
{
+ return pcm_resampler_global_init(error);
}
-PcmConvert::~PcmConvert()
+PcmConvert::PcmConvert()
{
+#ifndef NDEBUG
+ src_format.Clear();
+ dest_format.Clear();
+#endif
}
-void
-PcmConvert::Reset()
+PcmConvert::~PcmConvert()
{
- dsd.Reset();
- resampler.Reset();
+ assert(!src_format.IsValid());
+ assert(!dest_format.IsValid());
}
-inline const int16_t *
-PcmConvert::Convert16(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+bool
+PcmConvert::Open(const AudioFormat _src_format, const AudioFormat _dest_format,
+ Error &error)
{
- 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 == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 16 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
+ assert(!src_format.IsValid());
+ assert(!dest_format.IsValid());
+ assert(_src_format.IsValid());
+ assert(_dest_format.IsValid());
+
+ AudioFormat format = _src_format;
+ if (format.format == SampleFormat::DSD)
+ format.format = SampleFormat::FLOAT;
+
+ enable_resampler = format.sample_rate != _dest_format.sample_rate;
+ if (enable_resampler) {
+ if (!resampler.Open(format, _dest_format.sample_rate, error))
+ return false;
+
+ format.format = resampler.GetOutputSampleFormat();
+ format.sample_rate = _dest_format.sample_rate;
}
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_16(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
- return nullptr;
- }
+ enable_format = format.format != _dest_format.format;
+ if (enable_format &&
+ !format_converter.Open(format.format, _dest_format.format,
+ error)) {
+ if (enable_resampler)
+ resampler.Close();
+ return false;
}
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample16(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return nullptr;
+ format.format = _dest_format.format;
+
+ enable_channels = format.channels != _dest_format.channels;
+ if (enable_channels &&
+ !channels_converter.Open(format.format, format.channels,
+ _dest_format.channels, error)) {
+ if (enable_format)
+ format_converter.Close();
+ if (enable_resampler)
+ resampler.Close();
+ return false;
}
- *dest_size_r = len;
- return buf;
+ src_format = _src_format;
+ dest_format = _dest_format;
+
+ return true;
}
-inline const int32_t *
-PcmConvert::Convert24(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+void
+PcmConvert::Close()
{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format.format == SampleFormat::S24_P32);
-
- buf = pcm_convert_to_24(format_buffer,
- src_format.format,
- src_buffer, src_size, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 24 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
-
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_24(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
- return nullptr;
- }
- }
-
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample24(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return nullptr;
- }
+ if (enable_channels)
+ channels_converter.Close();
+ if (enable_format)
+ format_converter.Close();
+ if (enable_resampler)
+ resampler.Close();
+
+#ifdef ENABLE_DSD
+ dsd.Reset();
+#endif
- *dest_size_r = len;
- return buf;
+#ifndef NDEBUG
+ src_format.Clear();
+ dest_format.Clear();
+#endif
}
-inline const int32_t *
-PcmConvert::Convert32(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+ConstBuffer<void>
+PcmConvert::Convert(ConstBuffer<void> buffer, Error &error)
{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format.format == SampleFormat::S32);
-
- buf = pcm_convert_to_32(format_buffer,
- src_format.format,
- src_buffer, src_size, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 32 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
-
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_32(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
+#ifdef ENABLE_DSD
+ if (src_format.format == SampleFormat::DSD) {
+ auto s = ConstBuffer<uint8_t>::FromVoid(buffer);
+ auto d = dsd.ToFloat(src_format.channels, s);
+ if (d.IsNull()) {
+ error.Set(pcm_domain,
+ "DSD to PCM conversion failed");
return nullptr;
}
- }
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample32(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return buf;
+ buffer = d.ToVoid();
}
+#endif
- *dest_size_r = len;
- return buf;
-}
-
-inline const float *
-PcmConvert::ConvertFloat(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
-{
- const float *buffer = (const float *)src_buffer;
- size_t size = src_size;
-
- assert(dest_format.format == SampleFormat::FLOAT);
-
- /* convert to float now */
-
- buffer = pcm_convert_to_float(format_buffer,
- src_format.format,
- buffer, size, &size);
- if (buffer == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to float is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
-
- /* convert channels */
-
- if (src_format.channels != dest_format.channels) {
- buffer = pcm_convert_channels_float(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buffer, size, &size);
- if (buffer == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
+ if (enable_resampler) {
+ buffer = resampler.Resample(buffer, error);
+ if (buffer.IsNull())
return nullptr;
- }
}
- /* resample with float, because this is the best format for
- libsamplerate */
-
- if (src_format.sample_rate != dest_format.sample_rate) {
- buffer = resampler.ResampleFloat(dest_format.channels,
- src_format.sample_rate,
- buffer, size,
- dest_format.sample_rate,
- &size, error);
- if (buffer == nullptr)
+ if (enable_format) {
+ buffer = format_converter.Convert(buffer, error);
+ if (buffer.IsNull())
return nullptr;
}
- *dest_size_r = size;
- return buffer;
-}
-
-const void *
-PcmConvert::Convert(AudioFormat src_format,
- const void *src, size_t src_size,
- const AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error)
-{
- AudioFormat float_format;
- if (src_format.format == SampleFormat::DSD) {
- size_t f_size;
- const float *f = dsd.ToFloat(src_format.channels,
- false, (const uint8_t *)src,
- src_size, &f_size);
- if (f == nullptr) {
- error.Set(pcm_convert_domain,
- "DSD to PCM conversion failed");
+ if (enable_channels) {
+ buffer = channels_converter.Convert(buffer, error);
+ if (buffer.IsNull())
return nullptr;
- }
-
- float_format = src_format;
- float_format.format = SampleFormat::FLOAT;
-
- src_format = float_format;
- src = f;
- src_size = f_size;
}
- switch (dest_format.format) {
- case SampleFormat::S16:
- return Convert16(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::S24_P32:
- return Convert24(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::S32:
- return Convert32(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::FLOAT:
- return ConvertFloat(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- default:
- error.Format(pcm_convert_domain,
- "PCM conversion to %s is not implemented",
- sample_format_to_string(dest_format.format));
- return nullptr;
- }
+ return buffer;
}
diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx
index 40f785179..26ab02923 100644
--- a/src/pcm/PcmConvert.hxx
+++ b/src/pcm/PcmConvert.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,15 +20,22 @@
#ifndef PCM_CONVERT_HXX
#define PCM_CONVERT_HXX
-#include "PcmDither.hxx"
-#include "PcmDsd.hxx"
-#include "PcmResample.hxx"
+#include "check.h"
#include "PcmBuffer.hxx"
+#include "FormatConverter.hxx"
+#include "ChannelsConverter.hxx"
+#include "GlueResampler.hxx"
+#include "AudioFormat.hxx"
+
+#ifdef ENABLE_DSD
+#include "PcmDsd.hxx"
+#endif
#include <stddef.h>
-struct AudioFormat;
+template<typename T> struct ConstBuffer;
class Error;
+class Domain;
/**
* This object is statically allocated (within another struct), and
@@ -36,74 +43,48 @@ class Error;
* conversions.
*/
class PcmConvert {
+#ifdef ENABLE_DSD
PcmDsd dsd;
+#endif
- PcmResampler resampler;
-
- PcmDither dither;
+ GluePcmResampler resampler;
+ PcmFormatConverter format_converter;
+ PcmChannelsConverter channels_converter;
- /** the buffer for converting the sample format */
- PcmBuffer format_buffer;
+ AudioFormat src_format, dest_format;
- /** the buffer for converting the channel count */
- PcmBuffer channels_buffer;
+ bool enable_resampler, enable_format, enable_channels;
public:
PcmConvert();
~PcmConvert();
+ /**
+ * Prepare the object. Call Close() when done.
+ */
+ bool Open(AudioFormat _src_format, AudioFormat _dest_format,
+ Error &error);
/**
- * Reset the pcm_convert_state object. Use this at the
- * boundary between two distinct songs and each time the
- * format changes.
+ * Close the object after it was prepared with Open(). After
+ * that, it may be reused by calling Open() again.
*/
- void Reset();
+ void Close();
/**
* Converts PCM data between two audio formats.
*
* @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
+ * @param error_r location to store the error occurring, or nullptr to
* ignore errors
- * @return the destination buffer, or NULL on error
+ * @return the destination buffer, or nullptr on error
*/
- const void *Convert(AudioFormat src_format,
- const void *src, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
-private:
- const int16_t *Convert16(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const int32_t *Convert24(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const int32_t *Convert32(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const float *ConvertFloat(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
+ ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error);
};
-extern const class Domain pcm_convert_domain;
+bool
+pcm_convert_global_init(Error &error);
#endif
diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx
index 98d0d443e..7b2a9e900 100644
--- a/src/pcm/PcmDither.cxx
+++ b/src/pcm/PcmDither.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,18 +20,14 @@
#include "config.h"
#include "PcmDither.hxx"
#include "PcmPrng.hxx"
+#include "Traits.hxx"
-inline int16_t
-PcmDither::Dither24To16(int_fast32_t sample)
+template<typename T, T MIN, T MAX, unsigned scale_bits>
+inline T
+PcmDither::Dither(T sample)
{
- constexpr unsigned from_bits = 24;
- constexpr unsigned to_bits = 16;
- constexpr unsigned scale_bits = from_bits - to_bits;
- constexpr int_fast32_t round = 1 << (scale_bits - 1);
- constexpr int_fast32_t mask = (1 << scale_bits) - 1;
- constexpr int_fast32_t ONE = 1 << (from_bits - 1);
- constexpr int_fast32_t MIN = -ONE;
- constexpr int_fast32_t MAX = ONE - 1;
+ constexpr T round = 1 << (scale_bits - 1);
+ constexpr T mask = (1 << scale_bits) - 1;
sample += error[0] - error[1] + error[2];
@@ -39,9 +35,9 @@ PcmDither::Dither24To16(int_fast32_t sample)
error[1] = error[0] / 2;
/* round */
- int_fast32_t output = sample + round;
+ T output = sample + round;
- int_fast32_t rnd = pcm_prng(random);
+ const T rnd = pcm_prng(random);
output += (rnd & mask) - (random & mask);
random = rnd;
@@ -63,27 +59,59 @@ PcmDither::Dither24To16(int_fast32_t sample)
error[0] = sample - output;
- return (int16_t)(output >> scale_bits);
+ return output >> scale_bits;
}
-void
-PcmDither::Dither24To16(int16_t *dest, const int32_t *src,
- const int32_t *src_end)
+template<typename ST, unsigned SBITS, unsigned DBITS>
+inline ST
+PcmDither::DitherShift(ST sample)
+{
+ static_assert(sizeof(ST) * 8 > SBITS, "Source type too small");
+ static_assert(SBITS > DBITS, "Non-positive scale_bits");
+
+ static constexpr ST MIN = -(ST(1) << (SBITS - 1));
+ static constexpr ST MAX = (ST(1) << (SBITS - 1)) - 1;
+
+ return Dither<ST, MIN, MAX, SBITS - DBITS>(sample);
+}
+
+template<typename ST, typename DT>
+inline typename DT::value_type
+PcmDither::DitherConvert(typename ST::value_type sample)
+{
+ static_assert(ST::BITS > DT::BITS,
+ "Sample formats cannot be dithered");
+
+ constexpr unsigned scale_bits = ST::BITS - DT::BITS;
+
+ return Dither<typename ST::sum_type, ST::MIN, ST::MAX,
+ scale_bits>(sample);
+}
+
+template<typename ST, typename DT>
+inline void
+PcmDither::DitherConvert(typename DT::pointer_type dest,
+ typename ST::const_pointer_type src,
+ typename ST::const_pointer_type src_end)
{
while (src < src_end)
- *dest++ = Dither24To16(*src++);
+ *dest++ = DitherConvert<ST, DT>(*src++);
}
-inline int16_t
-PcmDither::Dither32To16(int_fast32_t sample)
+inline void
+PcmDither::Dither24To16(int16_t *dest, const int32_t *src,
+ const int32_t *src_end)
{
- return Dither24To16(sample >> 8);
+ typedef SampleTraits<SampleFormat::S24_P32> ST;
+ typedef SampleTraits<SampleFormat::S16> DT;
+ DitherConvert<ST, DT>(dest, src, src_end);
}
-void
+inline void
PcmDither::Dither32To16(int16_t *dest, const int32_t *src,
const int32_t *src_end)
{
- while (src < src_end)
- *dest++ = Dither32To16(*src++);
+ typedef SampleTraits<SampleFormat::S32> ST;
+ typedef SampleTraits<SampleFormat::S16> DT;
+ DitherConvert<ST, DT>(dest, src, src_end);
}
diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx
index 106382307..54b0f7315 100644
--- a/src/pcm/PcmDither.hxx
+++ b/src/pcm/PcmDither.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,8 @@
#include <stdint.h>
+enum class SampleFormat : uint8_t;
+
class PcmDither {
int32_t error[3];
int32_t random;
@@ -30,6 +32,18 @@ public:
constexpr PcmDither()
:error{0, 0, 0}, random(0) {}
+ /**
+ * Shift the given sample by #SBITS-#DBITS to the right, and
+ * apply dithering.
+ *
+ * @param ST the input sample type
+ * @param SBITS the input bit width
+ * @param DBITS the output bit width
+ * @param sample the input sample value
+ */
+ template<typename ST, unsigned SBITS, unsigned DBITS>
+ ST DitherShift(ST sample);
+
void Dither24To16(int16_t *dest, const int32_t *src,
const int32_t *src_end);
@@ -37,8 +51,34 @@ public:
const int32_t *src_end);
private:
- int16_t Dither24To16(int_fast32_t sample);
- int16_t Dither32To16(int_fast32_t sample);
+ /**
+ * Shift the given sample by #scale_bits to the right, and
+ * apply dithering.
+ *
+ * @param T the input sample type
+ * @param MIN the minimum input sample value
+ * @param MAX the maximum input sample value
+ * @param scale_bits the number of bits to be discarded
+ * @param sample the input sample value
+ */
+ template<typename T, T MIN, T MAX, unsigned scale_bits>
+ T Dither(T sample);
+
+ /**
+ * Convert the given sample from one sample format to another,
+ * discarding bits.
+ *
+ * @param ST the input #SampleTraits class
+ * @param ST the output #SampleTraits class
+ * @param sample the input sample value
+ */
+ template<typename ST, typename DT>
+ typename DT::value_type DitherConvert(typename ST::value_type sample);
+
+ template<typename ST, typename DT>
+ void DitherConvert(typename DT::pointer_type dest,
+ typename ST::const_pointer_type src,
+ typename ST::const_pointer_type src_end);
};
#endif
diff --git a/src/pcm/PcmDop.cxx b/src/pcm/PcmDop.cxx
new file mode 100644
index 000000000..b2096d9e4
--- /dev/null
+++ b/src/pcm/PcmDop.cxx
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PcmDop.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <assert.h>
+
+constexpr
+static inline uint32_t
+pcm_two_dsd_to_dop_marker1(uint8_t a, uint8_t b)
+{
+ return 0xff050000 | (a << 8) | b;
+}
+
+constexpr
+static inline uint32_t
+pcm_two_dsd_to_dop_marker2(uint8_t a, uint8_t b)
+{
+ return 0xfffa0000 | (a << 8) | b;
+}
+
+ConstBuffer<uint32_t>
+pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
+ ConstBuffer<uint8_t> _src)
+{
+ assert(audio_valid_channel_count(channels));
+ assert(!_src.IsNull());
+ 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;
+
+ uint32_t *const dest0 = (uint32_t *)buffer.GetT<uint32_t>(num_samples),
+ *dest = dest0;
+
+ auto src = _src.data;
+ 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_dop_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_dop_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, num_samples };
+}
diff --git a/src/pcm/PcmDop.hxx b/src/pcm/PcmDop.hxx
new file mode 100644
index 000000000..03161c456
--- /dev/null
+++ b/src/pcm/PcmDop.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_DOP_HXX
+#define MPD_PCM_DOP_HXX
+
+#include "check.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+class PcmBuffer;
+template<typename T> struct ConstBuffer;
+
+/**
+ * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for
+ * playback over USB, according to the DoP standard:
+ * http://dsd-guide.com/dop-open-standard
+ */
+ConstBuffer<uint32_t>
+pcm_dsd_to_dop(PcmBuffer &buffer, unsigned channels,
+ ConstBuffer<uint8_t> src);
+
+#endif
diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx
index 4db274635..53d26d480 100644
--- a/src/pcm/PcmDsd.cxx
+++ b/src/pcm/PcmDsd.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,11 +21,11 @@
#include "PcmDsd.hxx"
#include "dsd2pcm/dsd2pcm.h"
#include "util/Macros.hxx"
+#include "util/ConstBuffer.hxx"
#include <algorithm>
#include <assert.h>
-#include <string.h>
PcmDsd::PcmDsd()
{
@@ -47,22 +47,19 @@ PcmDsd::Reset()
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)
+ConstBuffer<float>
+PcmDsd::ToFloat(unsigned channels, ConstBuffer<uint8_t> src)
{
- assert(src != nullptr);
- assert(src_size > 0);
- assert(src_size % channels == 0);
+ assert(!src.IsNull());
+ assert(!src.IsEmpty());
+ assert(src.size % channels == 0);
assert(channels <= ARRAY_SIZE(dsd2pcm));
- const unsigned num_samples = src_size;
- const unsigned num_frames = src_size / channels;
+ const unsigned num_samples = src.size;
+ const unsigned num_frames = src.size / channels;
float *dest;
const size_t dest_size = num_samples * sizeof(*dest);
- *dest_size_r = dest_size;
dest = (float *)buffer.Get(dest_size);
for (unsigned c = 0; c < channels; ++c) {
@@ -73,9 +70,9 @@ PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
}
dsd2pcm_translate(dsd2pcm[c], num_frames,
- src + c, channels,
- lsbfirst, dest + c, channels);
+ src.data + c, channels,
+ false, dest + c, channels);
}
- return dest;
+ return { dest, num_samples };
}
diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx
index 26ee11b13..e3e3a3cb1 100644
--- a/src/pcm/PcmDsd.hxx
+++ b/src/pcm/PcmDsd.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,22 +25,24 @@
#include <stdint.h>
+template<typename T> struct ConstBuffer;
+
/**
* Wrapper for the dsd2pcm library.
*/
-struct PcmDsd {
+class PcmDsd {
PcmBuffer buffer;
struct dsd2pcm_ctx_s *dsd2pcm[32];
+public:
PcmDsd();
~PcmDsd();
void Reset();
- const float *ToFloat(unsigned channels, bool lsbfirst,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r);
+ ConstBuffer<float> ToFloat(unsigned channels,
+ ConstBuffer<uint8_t> src);
};
#endif
diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx
deleted file mode 100644
index 2d0f33a15..000000000
--- a/src/pcm/PcmDsdUsb.cxx
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PcmDsdUsb.hxx"
-#include "PcmBuffer.hxx"
-#include "AudioFormat.hxx"
-
-constexpr
-static inline uint32_t
-pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
-{
- return 0xff050000 | (a << 8) | b;
-}
-
-constexpr
-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
deleted file mode 100644
index 3b7121465..000000000
--- a/src/pcm/PcmDsdUsb.hxx
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PCM_DSD_USB_HXX
-#define MPD_PCM_DSD_USB_HXX
-
-#include "check.h"
-
-#include <stdint.h>
-#include <stddef.h>
-
-class 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
index f6ce1e661..ef099ba71 100644
--- a/src/pcm/PcmExport.cxx
+++ b/src/pcm/PcmExport.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,21 +19,24 @@
#include "config.h"
#include "PcmExport.hxx"
-#include "PcmDsdUsb.hxx"
+#include "PcmDop.hxx"
#include "PcmPack.hxx"
#include "util/ByteReverse.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <iterator>
void
PcmExport::Open(SampleFormat sample_format, unsigned _channels,
- bool _dsd_usb, bool _shift8, bool _pack, bool _reverse_endian)
+ bool _dop, bool _shift8, bool _pack, bool _reverse_endian)
{
assert(audio_valid_sample_format(sample_format));
- assert(!_dsd_usb || audio_valid_channel_count(_channels));
+ assert(!_dop || 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
+ dop = _dop && sample_format == SampleFormat::DSD;
+ if (dop)
+ /* after the conversion to DoP, the DSD
samples are stuffed inside fake 24 bit samples */
sample_format = SampleFormat::S24_P32;
@@ -61,7 +64,7 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
/* packed 24 bit samples (3 bytes per sample) */
return audio_format.channels * 3;
- if (dsd_usb)
+ if (dop)
/* 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
@@ -71,59 +74,47 @@ PcmExport::GetFrameSize(const AudioFormat &audio_format) const
return audio_format.GetFrameSize();
}
-const void *
-PcmExport::Export(const void *data, size_t size, size_t &dest_size_r)
+ConstBuffer<void>
+PcmExport::Export(ConstBuffer<void> data)
{
- if (dsd_usb)
- data = pcm_dsd_to_usb(dsd_buffer, channels,
- (const uint8_t *)data, size, &size);
+ if (dop)
+ data = pcm_dsd_to_dop(dop_buffer, channels,
+ ConstBuffer<uint8_t>::FromVoid(data))
+ .ToVoid();
if (pack24) {
- assert(size % 4 == 0);
-
- const size_t num_samples = size / 4;
+ const auto src = ConstBuffer<int32_t>::FromVoid(data);
+ const size_t num_samples = src.size;
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 != nullptr);
- pcm_pack_24(dest, (const int32_t *)src8,
- (const int32_t *)src_end8);
+ pcm_pack_24(dest, src.begin(), src.end());
- data = dest;
- size = dest_size;
+ data.data = dest;
+ data.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;
+ const auto src = ConstBuffer<int32_t>::FromVoid(data);
- uint32_t *dest = (uint32_t *)pack_buffer.Get(size);
- data = dest;
+ uint32_t *dest = (uint32_t *)pack_buffer.Get(data.size);
+ data.data = dest;
- while (src < src_end)
- *dest++ = *src++ << 8;
+ for (auto i : src)
+ *dest++ = i << 8;
}
-
if (reverse_endian > 0) {
assert(reverse_endian >= 2);
- uint8_t *dest = (uint8_t *)reverse_buffer.Get(size);
- assert(dest != nullptr);
+ const auto src = ConstBuffer<uint8_t>::FromVoid(data);
- const uint8_t *src = (const uint8_t *)data;
- const uint8_t *src_end = src + size;
- reverse_bytes(dest, src, src_end, reverse_endian);
+ uint8_t *dest = (uint8_t *)reverse_buffer.Get(data.size);
+ assert(dest != nullptr);
+ data.data = dest;
- data = dest;
+ reverse_bytes(dest, src.begin(), src.end(), reverse_endian);
}
- dest_size_r = size;
return data;
}
@@ -134,8 +125,8 @@ PcmExport::CalcSourceSize(size_t size) const
/* 32 bit to 24 bit conversion (4 to 3 bytes) */
size = (size / 3) * 4;
- if (dsd_usb)
- /* DSD over USB doubles the transport size */
+ if (dop)
+ /* DoP doubles the transport size */
size /= 2;
return size;
diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx
index bd18c0534..b99a35835 100644
--- a/src/pcm/PcmExport.hxx
+++ b/src/pcm/PcmExport.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#include "AudioFormat.hxx"
struct AudioFormat;
+template<typename T> struct ConstBuffer;
/**
* An object that handles export of PCM samples to some instance
@@ -34,11 +35,11 @@ struct AudioFormat;
struct PcmExport {
/**
* The buffer is used to convert DSD samples to the
- * DSD-over-USB format.
+ * DoP format.
*
- * @see #dsd_usb
+ * @see #dop
*/
- PcmBuffer dsd_buffer;
+ PcmBuffer dop_buffer;
/**
* The buffer is used to pack samples, removing padding.
@@ -60,11 +61,11 @@ struct PcmExport {
uint8_t channels;
/**
- * Convert DSD to DSD-over-USB? Input format must be
+ * Convert DSD to DSD-over-PCM (DoP)? Input format must be
* SampleFormat::DSD and output format must be
* SampleFormat::S24_P32.
*/
- bool dsd_usb;
+ bool dop;
/**
* Convert (padded) 24 bit samples to 32 bit by shifting 8
@@ -88,14 +89,14 @@ struct PcmExport {
* 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.
+ * times to reuse the object.
*
* This function cannot fail.
*
- * @param channels the number of channels; ignored unless dsd_usb is set
+ * @param channels the number of channels; ignored unless dop is set
*/
void Open(SampleFormat sample_format, unsigned channels,
- bool dsd_usb, bool shift8, bool pack, bool reverse_endian);
+ bool dop, bool shift8, bool pack, bool reverse_endian);
/**
* Calculate the size of one output frame.
@@ -106,14 +107,10 @@ struct PcmExport {
/**
* 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);
+ ConstBuffer<void> Export(ConstBuffer<void> src);
/**
* Converts the number of consumed bytes from the pcm_export()
diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx
index 4565c71c6..4cabc05a0 100644
--- a/src/pcm/PcmFormat.cxx
+++ b/src/pcm/PcmFormat.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,171 +19,150 @@
#include "config.h"
#include "PcmFormat.hxx"
-#include "PcmDither.hxx"
#include "PcmBuffer.hxx"
#include "PcmUtils.hxx"
+#include "Traits.hxx"
+#include "FloatConvert.hxx"
+#include "ShiftConvert.hxx"
+#include "util/ConstBuffer.hxx"
-#include <type_traits>
+#include "PcmDither.cxx" // including the .cxx file to get inlined templates
-template<SampleFormat F>
-struct SampleTraits {};
+/**
+ * Wrapper for a class that converts one sample at a time into one
+ * that converts a buffer at a time.
+ */
+template<typename C>
+struct PerSampleConvert : C {
+ typedef typename C::SrcTraits SrcTraits;
+ typedef typename C::DstTraits DstTraits;
+
+ void Convert(typename DstTraits::pointer_type gcc_restrict out,
+ typename SrcTraits::const_pointer_type gcc_restrict in,
+ size_t n) const {
+ for (size_t i = 0; i != n; ++i)
+ out[i] = C::Convert(in[i]);
+ }
+};
-template<>
-struct SampleTraits<SampleFormat::S8> {
- typedef int8_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
+struct Convert8To16
+ : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S8,
+ SampleFormat::S16>> {};
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = sizeof(value_type) * 8;
-};
+struct Convert24To16 {
+ typedef SampleTraits<SampleFormat::S24_P32> SrcTraits;
+ typedef SampleTraits<SampleFormat::S16> DstTraits;
-template<>
-struct SampleTraits<SampleFormat::S16> {
- typedef int16_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
+ PcmDither &dither;
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = sizeof(value_type) * 8;
+ Convert24To16(PcmDither &_dither):dither(_dither) {}
+
+ void Convert(int16_t *out, const int32_t *in, size_t n) {
+ dither.Dither24To16(out, in, in + n);
+ }
};
-template<>
-struct SampleTraits<SampleFormat::S32> {
- typedef int32_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
+struct Convert32To16 {
+ typedef SampleTraits<SampleFormat::S32> SrcTraits;
+ typedef SampleTraits<SampleFormat::S16> DstTraits;
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = sizeof(value_type) * 8;
-};
+ PcmDither &dither;
-template<>
-struct SampleTraits<SampleFormat::S24_P32> {
- typedef int32_t value_type;
- typedef value_type *pointer_type;
- typedef const value_type *const_pointer_type;
+ Convert32To16(PcmDither &_dither):dither(_dither) {}
- static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
- static constexpr unsigned BITS = 24;
+ void Convert(int16_t *out, const int32_t *in, size_t n) {
+ dither.Dither32To16(out, in, in + n);
+ }
};
-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;
- }
-}
+template<SampleFormat F, class Traits=SampleTraits<F>>
+struct PortableFloatToInteger
+ : PerSampleConvert<FloatToIntegerSampleConvert<F, Traits>> {};
-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);
-}
+template<SampleFormat F, class Traits=SampleTraits<F>>
+struct FloatToInteger : PortableFloatToInteger<F, Traits> {};
-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);
-}
+/**
+ * A template class that attempts to use the "optimized" algorithm for
+ * large portions of the buffer, and calls the "portable" algorithm"
+ * for the rest when the last block is not full.
+ */
+template<typename Optimized, typename Portable>
+class GlueOptimizedConvert : Optimized, Portable {
+public:
+ typedef typename Portable::SrcTraits SrcTraits;
+ typedef typename Portable::DstTraits DstTraits;
+
+ void Convert(typename DstTraits::pointer_type out,
+ typename SrcTraits::const_pointer_type in,
+ size_t n) const {
+ Optimized::Convert(out, in, n);
+
+ /* use the "portable" algorithm for the trailing
+ samples */
+ size_t remaining = n % Optimized::BLOCK_SIZE;
+ size_t done = n - remaining;
+ Portable::Convert(out + done, in + done, remaining);
+ }
+};
-template<SampleFormat F, class Traits=SampleTraits<F>>
-static void
-ConvertFromFloat(typename Traits::pointer_type dest,
- const float *src, const float *end)
-{
- constexpr auto bits = Traits::BITS;
+#ifdef __ARM_NEON__
+#include "Neon.hxx"
- const float factor = 1 << (bits - 1);
+template<>
+struct FloatToInteger<SampleFormat::S16, SampleTraits<SampleFormat::S16>>
+ : GlueOptimizedConvert<NeonFloatTo16,
+ PortableFloatToInteger<SampleFormat::S16>> {};
- while (src != end) {
- int sample(*src++ * factor);
- *dest++ = PcmClamp<typename Traits::value_type, int, bits>(sample);
- }
-}
+#endif
-template<SampleFormat F, class Traits=SampleTraits<F>>
-static void
-ConvertFromFloat(typename Traits::pointer_type dest,
- const float *src, size_t size)
+template<class C>
+static ConstBuffer<typename C::DstTraits::value_type>
+AllocateConvert(PcmBuffer &buffer, C convert,
+ ConstBuffer<typename C::SrcTraits::value_type> src)
{
- ConvertFromFloat<F, Traits>(dest, src,
- pcm_end_pointer(src, size));
+ auto dest = buffer.GetT<typename C::DstTraits::value_type>(src.size);
+ convert.Convert(dest, src.data, src.size);
+ return { dest, src.size };
}
template<SampleFormat F, class Traits=SampleTraits<F>>
-static typename Traits::pointer_type
-AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<typename Traits::value_type>
+AllocateFromFloat(PcmBuffer &buffer, ConstBuffer<float> src)
{
- 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(typename Traits::value_type);
- auto dest = (typename Traits::pointer_type)buffer.Get(*dest_size_r);
- ConvertFromFloat<F, Traits>(dest, src, src_size);
- return dest;
+ return AllocateConvert(buffer, FloatToInteger<F, Traits>(), src);
}
-static int16_t *
-pcm_allocate_8_to_16(PcmBuffer &buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int16_t>
+pcm_allocate_8_to_16(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- int16_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = (int16_t *)buffer.Get(*dest_size_r);
- pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ return AllocateConvert(buffer, Convert8To16(), src);
}
-static int16_t *
+static ConstBuffer<int16_t>
pcm_allocate_24p32_to_16(PcmBuffer &buffer, PcmDither &dither,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+ ConstBuffer<int32_t> src)
{
- 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;
+ return AllocateConvert(buffer, Convert24To16(dither), src);
}
-static int16_t *
+static ConstBuffer<int16_t>
pcm_allocate_32_to_16(PcmBuffer &buffer, PcmDither &dither,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+ ConstBuffer<int32_t> src)
{
- 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;
+ return AllocateConvert(buffer, Convert32To16(dither), src);
}
-static int16_t *
-pcm_allocate_float_to_16(PcmBuffer &buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int16_t>
+pcm_allocate_float_to_16(PcmBuffer &buffer, ConstBuffer<float> src)
{
- return AllocateFromFloat<SampleFormat::S16>(buffer, src, src_size,
- dest_size_r);
+ return AllocateFromFloat<SampleFormat::S16>(buffer, src);
}
-const int16_t *
+ConstBuffer<int16_t>
pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
- assert(src_size % sample_format_size(src_format) == 0);
-
switch (src_format) {
case SampleFormat::UNDEFINED:
case SampleFormat::DSD:
@@ -191,104 +170,67 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
case SampleFormat::S8:
return pcm_allocate_8_to_16(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
- *dest_size_r = src_size;
- return (const int16_t *)src;
+ return ConstBuffer<int16_t>::FromVoid(src);
case SampleFormat::S24_P32:
return pcm_allocate_24p32_to_16(buffer, dither,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::S32:
return pcm_allocate_32_to_16(buffer, dither,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::FLOAT:
return pcm_allocate_float_to_16(buffer,
- (const float *)src, src_size,
- dest_size_r);
+ ConstBuffer<float>::FromVoid(src));
}
return nullptr;
}
-static void
-pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 16;
-}
+struct Convert8To24
+ : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S8,
+ SampleFormat::S24_P32>> {};
-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;
-}
+struct Convert16To24
+ : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S16,
+ SampleFormat::S24_P32>> {};
-static void
-pcm_convert_32_to_24(int32_t *gcc_restrict out,
- const int32_t *gcc_restrict in,
- const int32_t *gcc_restrict in_end)
+static ConstBuffer<int32_t>
+pcm_allocate_8_to_24(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- while (in < in_end)
- *out++ = *in++ >> 8;
+ return AllocateConvert(buffer, Convert8To24(), src);
}
-static int32_t *
-pcm_allocate_8_to_24(PcmBuffer &buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_16_to_24(PcmBuffer &buffer, ConstBuffer<int16_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ return AllocateConvert(buffer, Convert16To24(), src);
}
-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;
-}
+struct Convert32To24
+ : PerSampleConvert<RightShiftSampleConvert<SampleFormat::S32,
+ SampleFormat::S24_P32>> {};
-static int32_t *
-pcm_allocate_32_to_24(PcmBuffer &buffer,
- const int32_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_32_to_24(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- *dest_size_r = src_size;
- int32_t *dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ return AllocateConvert(buffer, Convert32To24(), src);
}
-static int32_t *
-pcm_allocate_float_to_24(PcmBuffer &buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_float_to_24(PcmBuffer &buffer, ConstBuffer<float> src)
{
- return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src, src_size,
- dest_size_r);
+ return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src);
}
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_to_24(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
- assert(src_size % sample_format_size(src_format) == 0);
-
switch (src_format) {
case SampleFormat::UNDEFINED:
case SampleFormat::DSD:
@@ -296,110 +238,67 @@ pcm_convert_to_24(PcmBuffer &buffer,
case SampleFormat::S8:
return pcm_allocate_8_to_24(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
return pcm_allocate_16_to_24(buffer,
- (const int16_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int16_t>::FromVoid(src));
case SampleFormat::S24_P32:
- *dest_size_r = src_size;
- return (const int32_t *)src;
+ return ConstBuffer<int32_t>::FromVoid(src);
case SampleFormat::S32:
return pcm_allocate_32_to_24(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::FLOAT:
return pcm_allocate_float_to_24(buffer,
- (const float *)src, src_size,
- dest_size_r);
+ ConstBuffer<float>::FromVoid(src));
}
return nullptr;
}
-static void
-pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 24;
-}
+struct Convert8To32
+ : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S8,
+ SampleFormat::S32>> {};
-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;
-}
+struct Convert16To32
+ : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S16,
+ SampleFormat::S32>> {};
-static void
-pcm_convert_24_to_32(int32_t *gcc_restrict out,
- const int32_t *gcc_restrict in,
- const int32_t *gcc_restrict in_end)
-{
- while (in < in_end)
- *out++ = *in++ << 8;
-}
+struct Convert24To32
+ : PerSampleConvert<LeftShiftSampleConvert<SampleFormat::S24_P32,
+ SampleFormat::S32>> {};
-static int32_t *
-pcm_allocate_8_to_32(PcmBuffer &buffer,
- const int8_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_8_to_32(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ return AllocateConvert(buffer, Convert8To32(), src);
}
-static int32_t *
-pcm_allocate_16_to_32(PcmBuffer &buffer,
- const int16_t *src, size_t src_size, size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_16_to_32(PcmBuffer &buffer, ConstBuffer<int16_t> src)
{
- int32_t *dest;
- *dest_size_r = src_size * 2;
- assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
- dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ return AllocateConvert(buffer, Convert16To32(), src);
}
-static int32_t *
-pcm_allocate_24p32_to_32(PcmBuffer &buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_24p32_to_32(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- *dest_size_r = src_size;
- int32_t *dest = (int32_t *)buffer.Get(*dest_size_r);
- pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size));
- return dest;
+ return AllocateConvert(buffer, Convert24To32(), src);
}
-static int32_t *
-pcm_allocate_float_to_32(PcmBuffer &buffer,
- const float *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<int32_t>
+pcm_allocate_float_to_32(PcmBuffer &buffer, ConstBuffer<float> src)
{
- /* convert to S24_P32 first */
- int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
- dest_size_r);
-
- /* convert to 32 bit in-place */
- pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
- return dest;
+ return AllocateFromFloat<SampleFormat::S32>(buffer, src);
}
-const int32_t *
+ConstBuffer<int32_t>
pcm_convert_to_32(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
- assert(src_size % sample_format_size(src_format) == 0);
-
switch (src_format) {
case SampleFormat::UNDEFINED:
case SampleFormat::DSD:
@@ -407,108 +306,66 @@ pcm_convert_to_32(PcmBuffer &buffer,
case SampleFormat::S8:
return pcm_allocate_8_to_32(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
return pcm_allocate_16_to_32(buffer,
- (const int16_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int16_t>::FromVoid(src));
case SampleFormat::S24_P32:
return pcm_allocate_24p32_to_32(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::S32:
- *dest_size_r = src_size;
- return (const int32_t *)src;
+ return ConstBuffer<int32_t>::FromVoid(src);
case SampleFormat::FLOAT:
return pcm_allocate_float_to_32(buffer,
- (const float *)src, src_size,
- dest_size_r);
+ ConstBuffer<float>::FromVoid(src));
}
return nullptr;
}
-template<SampleFormat F, class Traits=SampleTraits<F>>
-static void
-ConvertToFloat(float *dest,
- typename Traits::const_pointer_type src,
- typename Traits::const_pointer_type end)
-{
- constexpr float factor = 0.5 / (1 << (Traits::BITS - 2));
- while (src != end)
- *dest++ = float(*src++) * factor;
+struct Convert8ToFloat
+ : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S8>> {};
-}
+struct Convert16ToFloat
+ : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S16>> {};
-template<SampleFormat F, class Traits=SampleTraits<F>>
-static void
-ConvertToFloat(float *dest,
- typename Traits::const_pointer_type src, size_t size)
-{
- ConvertToFloat<F, Traits>(dest, src, pcm_end_pointer(src, size));
-}
+struct Convert24ToFloat
+ : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S24_P32>> {};
-template<SampleFormat F, class Traits=SampleTraits<F>>
-static float *
-AllocateToFloat(PcmBuffer &buffer,
- typename Traits::const_pointer_type src, size_t src_size,
- size_t *dest_size_r)
-{
- constexpr size_t src_sample_size = Traits::SAMPLE_SIZE;
- assert(src_size % src_sample_size == 0);
-
- const size_t num_samples = src_size / src_sample_size;
- *dest_size_r = num_samples * sizeof(float);
- float *dest = (float *)buffer.Get(*dest_size_r);
- ConvertToFloat<F, Traits>(dest, src, src_size);
- return dest;
-}
+struct Convert32ToFloat
+ : PerSampleConvert<IntegerToFloatSampleConvert<SampleFormat::S32>> {};
-static float *
-pcm_allocate_8_to_float(PcmBuffer &buffer,
- const int8_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_8_to_float(PcmBuffer &buffer, ConstBuffer<int8_t> src)
{
- return AllocateToFloat<SampleFormat::S8>(buffer, src, src_size,
- dest_size_r);
+ return AllocateConvert(buffer, Convert8ToFloat(), src);
}
-static float *
-pcm_allocate_16_to_float(PcmBuffer &buffer,
- const int16_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_16_to_float(PcmBuffer &buffer, ConstBuffer<int16_t> src)
{
- return AllocateToFloat<SampleFormat::S16>(buffer, src, src_size,
- dest_size_r);
+ return AllocateConvert(buffer, Convert16ToFloat(), src);
}
-static float *
-pcm_allocate_24p32_to_float(PcmBuffer &buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_24p32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- return AllocateToFloat<SampleFormat::S24_P32>(buffer, src, src_size,
- dest_size_r);
+ return AllocateConvert(buffer, Convert24ToFloat(), src);
}
-static float *
-pcm_allocate_32_to_float(PcmBuffer &buffer,
- const int32_t *src, size_t src_size,
- size_t *dest_size_r)
+static ConstBuffer<float>
+pcm_allocate_32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src)
{
- return AllocateToFloat<SampleFormat::S32>(buffer, src, src_size,
- dest_size_r);
+ return AllocateConvert(buffer, Convert32ToFloat(), src);
}
-const float *
+ConstBuffer<float>
pcm_convert_to_float(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r)
+ SampleFormat src_format, ConstBuffer<void> src)
{
switch (src_format) {
case SampleFormat::UNDEFINED:
@@ -517,27 +374,22 @@ pcm_convert_to_float(PcmBuffer &buffer,
case SampleFormat::S8:
return pcm_allocate_8_to_float(buffer,
- (const int8_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int8_t>::FromVoid(src));
case SampleFormat::S16:
return pcm_allocate_16_to_float(buffer,
- (const int16_t *)src, src_size,
- dest_size_r);
-
- case SampleFormat::S24_P32:
- return pcm_allocate_24p32_to_float(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int16_t>::FromVoid(src));
case SampleFormat::S32:
return pcm_allocate_32_to_float(buffer,
- (const int32_t *)src, src_size,
- dest_size_r);
+ ConstBuffer<int32_t>::FromVoid(src));
+
+ case SampleFormat::S24_P32:
+ return pcm_allocate_24p32_to_float(buffer,
+ ConstBuffer<int32_t>::FromVoid(src));
case SampleFormat::FLOAT:
- *dest_size_r = src_size;
- return (const float *)src;
+ return ConstBuffer<float>::FromVoid(src);
}
return nullptr;
diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx
index cc44d6dd5..da182e771 100644
--- a/src/pcm/PcmFormat.hxx
+++ b/src/pcm/PcmFormat.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#include <stdint.h>
#include <stddef.h>
+template<typename T> struct ConstBuffer;
class PcmBuffer;
class PcmDither;
@@ -36,14 +37,12 @@ class PcmDither;
* @param dither a pcm_dither object for 24-to-16 conversion
* @param bits the number of in the source buffer
* @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int16_t *
+gcc_pure
+ConstBuffer<int16_t>
pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
/**
* Converts PCM samples to 24 bit (32 bit alignment).
@@ -51,14 +50,12 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither,
* @param buffer a PcmBuffer object
* @param bits the number of in the source buffer
* @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+gcc_pure
+ConstBuffer<int32_t>
pcm_convert_to_24(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
/**
* Converts PCM samples to 32 bit.
@@ -66,14 +63,12 @@ pcm_convert_to_24(PcmBuffer &buffer,
* @param buffer a PcmBuffer object
* @param bits the number of in the source buffer
* @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const int32_t *
+gcc_pure
+ConstBuffer<int32_t>
pcm_convert_to_32(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
/**
* Converts PCM samples to 32 bit floating point.
@@ -85,9 +80,9 @@ pcm_convert_to_32(PcmBuffer &buffer,
* @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
-const float *
+gcc_pure
+ConstBuffer<float>
pcm_convert_to_float(PcmBuffer &buffer,
- SampleFormat src_format, const void *src,
- size_t src_size, size_t *dest_size_r);
+ SampleFormat src_format, ConstBuffer<void> src);
#endif
diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx
index 001794061..d21b5f04b 100644
--- a/src/pcm/PcmMix.cxx
+++ b/src/pcm/PcmMix.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,42 +19,56 @@
#include "config.h"
#include "PcmMix.hxx"
-#include "PcmVolume.hxx"
+#include "Volume.hxx"
#include "PcmUtils.hxx"
#include "AudioFormat.hxx"
+#include "Traits.hxx"
+#include "util/Clamp.hxx"
+#include "PcmDither.cxx" // including the .cxx file to get inlined templates
+
+#include <assert.h>
#include <math.h>
-template<typename T, typename U, unsigned bits>
-static T
-PcmAddVolume(T _a, T _b, int volume1, int volume2)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::value_type
+PcmAddVolume(PcmDither &dither,
+ typename Traits::value_type _a, typename Traits::value_type _b,
+ int volume1, int volume2)
{
- U a(_a), b(_b);
-
- U c = ((a * volume1 + b * volume2) +
- pcm_volume_dither() + PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
+ typename Traits::long_type a(_a), b(_b);
+ typename Traits::long_type c(a * volume1 + b * volume2);
- return PcmClamp<T, U, bits>(c);
+ return dither.DitherShift<typename Traits::long_type,
+ Traits::BITS + PCM_VOLUME_BITS,
+ Traits::BITS>(c);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
-PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2)
+PcmAddVolume(PcmDither &dither,
+ typename Traits::pointer_type a,
+ typename Traits::const_pointer_type b,
+ size_t n, int volume1, int volume2)
{
for (size_t i = 0; i != n; ++i)
- a[i] = PcmAddVolume<T, U, bits>(a[i], b[i], volume1, volume2);
+ a[i] = PcmAddVolume<F, Traits>(dither, a[i], b[i],
+ volume1, volume2);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
-PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2)
+PcmAddVolumeVoid(PcmDither &dither,
+ void *a, const void *b, size_t size, int volume1, int volume2)
{
- constexpr size_t sample_size = sizeof(T);
+ constexpr size_t sample_size = Traits::SAMPLE_SIZE;
assert(size % sample_size == 0);
- PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size,
- volume1, volume2);
+ PcmAddVolume<F, Traits>(dither,
+ typename Traits::pointer_type(a),
+ typename Traits::const_pointer_type(b),
+ size / sample_size,
+ volume1, volume2);
}
static void
@@ -72,7 +86,7 @@ pcm_add_vol_float(float *buffer1, const float *buffer2,
}
static bool
-pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
+pcm_add_vol(PcmDither &dither, void *buffer1, const void *buffer2, size_t size,
int vol1, int vol2,
SampleFormat format)
{
@@ -83,23 +97,27 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
return false;
case SampleFormat::S8:
- PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S8>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::S16:
- PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S16>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::S24_P32:
- PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S24_P32>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::S32:
- PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size,
- vol1, vol2);
+ PcmAddVolumeVoid<SampleFormat::S32>(dither,
+ buffer1, buffer2, size,
+ vol1, vol2);
return true;
case SampleFormat::FLOAT:
@@ -114,30 +132,35 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
gcc_unreachable();
}
-template<typename T, typename U, unsigned bits>
-static T
-PcmAdd(T _a, T _b)
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static typename Traits::value_type
+PcmAdd(typename Traits::value_type _a, typename Traits::value_type _b)
{
- U a(_a), b(_b);
- return PcmClamp<T, U, bits>(a + b);
+ typename Traits::sum_type a(_a), b(_b);
+
+ return PcmClamp<F, Traits>(a + b);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
-PcmAdd(T *a, const T *b, unsigned n)
+PcmAdd(typename Traits::pointer_type a,
+ typename Traits::const_pointer_type b,
+ size_t n)
{
for (size_t i = 0; i != n; ++i)
- a[i] = PcmAdd<T, U, bits>(a[i], b[i]);
+ a[i] = PcmAdd<F, Traits>(a[i], b[i]);
}
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
static void
PcmAddVoid(void *a, const void *b, size_t size)
{
- constexpr size_t sample_size = sizeof(T);
+ constexpr size_t sample_size = Traits::SAMPLE_SIZE;
assert(size % sample_size == 0);
- PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size);
+ PcmAdd<F, Traits>(typename Traits::pointer_type(a),
+ typename Traits::const_pointer_type(b),
+ size / sample_size);
}
static void
@@ -162,19 +185,19 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
return false;
case SampleFormat::S8:
- PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S8>(buffer1, buffer2, size);
return true;
case SampleFormat::S16:
- PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S16>(buffer1, buffer2, size);
return true;
case SampleFormat::S24_P32:
- PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S24_P32>(buffer1, buffer2, size);
return true;
case SampleFormat::S32:
- PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size);
+ PcmAddVoid<SampleFormat::S32>(buffer1, buffer2, size);
return true;
case SampleFormat::FLOAT:
@@ -188,10 +211,9 @@ pcm_add(void *buffer1, const void *buffer2, size_t size,
}
bool
-pcm_mix(void *buffer1, const void *buffer2, size_t size,
+pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size,
SampleFormat format, float portion1)
{
- int vol1;
float s;
/* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses -1
@@ -202,8 +224,9 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size,
s = sin(M_PI_2 * portion1);
s *= s;
- vol1 = s * PCM_VOLUME_1 + 0.5;
- vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1);
+ int vol1 = s * PCM_VOLUME_1S + 0.5;
+ vol1 = Clamp<int>(vol1, 0, PCM_VOLUME_1S);
- return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
+ return pcm_add_vol(dither, buffer1, buffer2, size,
+ vol1, PCM_VOLUME_1S - vol1, format);
}
diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx
index 637c88f8a..4e22a33f1 100644
--- a/src/pcm/PcmMix.hxx
+++ b/src/pcm/PcmMix.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,8 @@
#include <stddef.h>
+class PcmDither;
+
/*
* Linearly mixes two PCM buffers. Both must have the same length and
* the same audio format. The formula is:
@@ -44,7 +46,7 @@
*/
gcc_warn_unused_result
bool
-pcm_mix(void *buffer1, const void *buffer2, size_t size,
+pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size,
SampleFormat format, float portion1);
#endif
diff --git a/src/pcm/PcmPack.cxx b/src/pcm/PcmPack.cxx
index 8920eb288..7a3379ad0 100644
--- a/src/pcm/PcmPack.cxx
+++ b/src/pcm/PcmPack.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/pcm/PcmPack.hxx b/src/pcm/PcmPack.hxx
index aed011767..271a3cd25 100644
--- a/src/pcm/PcmPack.hxx
+++ b/src/pcm/PcmPack.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/pcm/PcmPrng.hxx b/src/pcm/PcmPrng.hxx
index 0c823250d..5233caba6 100644
--- a/src/pcm/PcmPrng.hxx
+++ b/src/pcm/PcmPrng.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,7 +24,7 @@
* A very simple linear congruential PRNG. It's good enough for PCM
* dithering.
*/
-static unsigned long
+constexpr static inline unsigned long
pcm_prng(unsigned long state)
{
return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx
deleted file mode 100644
index 01f269ea9..000000000
--- a/src/pcm/PcmResample.cxx
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PcmResampleInternal.hxx"
-
-#ifdef HAVE_LIBSAMPLERATE
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
-#endif
-
-#include <string.h>
-
-#ifdef HAVE_LIBSAMPLERATE
-static bool lsr_enabled;
-#endif
-
-#ifdef HAVE_LIBSAMPLERATE
-static bool
-pcm_resample_lsr_enabled(void)
-{
- return lsr_enabled;
-}
-#endif
-
-bool
-pcm_resample_global_init(Error &error)
-{
-#ifdef HAVE_LIBSAMPLERATE
- const char *converter =
- config_get_string(CONF_SAMPLERATE_CONVERTER, "");
-
- lsr_enabled = strcmp(converter, "internal") != 0;
- if (lsr_enabled)
- return pcm_resample_lsr_global_init(converter, error);
- else
- return true;
-#else
- (void)error;
- return true;
-#endif
-}
-
-PcmResampler::PcmResampler()
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- pcm_resample_lsr_init(this);
-#endif
-}
-
-PcmResampler::~PcmResampler()
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- pcm_resample_lsr_deinit(this);
-#endif
-}
-
-void
-PcmResampler::Reset()
-{
-#ifdef HAVE_LIBSAMPLERATE
- pcm_resample_lsr_reset(this);
-#endif
-}
-
-const float *
-PcmResampler::ResampleFloat(unsigned channels, unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_float(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- /* sizeof(float)==sizeof(int32_t); the fallback resampler does
- not do any math on the sample values, so this hack is
- possible: */
- return (const float *)
- pcm_resample_fallback_32(this, channels,
- src_rate, (const int32_t *)src_buffer,
- src_size,
- dest_rate, dest_size_r);
-}
-
-const int16_t *
-PcmResampler::Resample16(unsigned channels,
- unsigned src_rate, const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_16(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- return pcm_resample_fallback_16(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
-
-const int32_t *
-PcmResampler::Resample32(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_32(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- return pcm_resample_fallback_32(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
-
-const int32_t *
-PcmResampler::Resample24(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r)
-{
-#ifdef HAVE_LIBSAMPLERATE
- if (pcm_resample_lsr_enabled())
- return pcm_resample_lsr_24(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error_r);
-#else
- (void)error_r;
-#endif
-
- /* reuse the 32 bit code - the resampler code doesn't care if
- the upper 8 bits are actually used */
- return pcm_resample_fallback_32(this, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
-}
diff --git a/src/pcm/PcmResample.hxx b/src/pcm/PcmResample.hxx
deleted file mode 100644
index e839d6ecd..000000000
--- a/src/pcm/PcmResample.hxx
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PCM_RESAMPLE_HXX
-#define MPD_PCM_RESAMPLE_HXX
-
-#include "check.h"
-#include "PcmBuffer.hxx"
-
-#include <stdint.h>
-#include <stddef.h>
-
-#ifdef HAVE_LIBSAMPLERATE
-#include <samplerate.h>
-#endif
-
-class Error;
-
-/**
- * This object is statically allocated (within another struct), and
- * holds buffer allocations and the state for the resampler.
- */
-struct PcmResampler {
-#ifdef HAVE_LIBSAMPLERATE
- SRC_STATE *state;
- SRC_DATA data;
-
- PcmBuffer in, out;
-
- struct {
- unsigned src_rate;
- unsigned dest_rate;
- unsigned channels;
- } prev;
-
- int error;
-#endif
-
- PcmBuffer buffer;
-
- PcmResampler();
- ~PcmResampler();
-
- /**
- * @see pcm_convert_reset()
- */
- void Reset();
-
- /**
- * Resamples 32 bit float data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const float *ResampleFloat(unsigned channels, unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-
- /**
- * Resamples 16 bit PCM data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const int16_t *Resample16(unsigned channels, unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-
- /**
- * Resamples 32 bit PCM data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const int32_t *Resample32(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-
- /**
- * Resamples 24 bit PCM data.
- *
- * @param channels the number of channels
- * @param src_rate the source sample rate
- * @param src the source PCM buffer
- * @param src_size the size of #src in bytes
- * @param dest_rate the requested destination sample rate
- * @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
- */
- const int32_t *Resample24(unsigned channels, unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error_r);
-};
-
-bool
-pcm_resample_global_init(Error &error);
-
-#endif
diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx
deleted file mode 100644
index a62cd64f7..000000000
--- a/src/pcm/PcmResampleFallback.cxx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PcmResampleInternal.hxx"
-
-#include <assert.h>
-
-/* resampling code blatantly ripped from ESD */
-const int16_t *
-pcm_resample_fallback_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
-{
- unsigned dest_pos = 0;
- unsigned src_frames = src_size / channels / sizeof(*src_buffer);
- unsigned dest_frames =
- (src_frames * dest_rate + src_rate - 1) / src_rate;
- unsigned dest_samples = dest_frames * channels;
- size_t dest_size = dest_samples * sizeof(*src_buffer);
- int16_t *dest_buffer = (int16_t *)state->buffer.Get(dest_size);
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- switch (channels) {
- case 1:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- }
- break;
- case 2:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
- src_pos &= ~1;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- dest_buffer[dest_pos++] = src_buffer[src_pos + 1];
- }
- break;
- }
-
- *dest_size_r = dest_size;
- return dest_buffer;
-}
-
-const int32_t *
-pcm_resample_fallback_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
-{
- unsigned dest_pos = 0;
- unsigned src_frames = src_size / channels / sizeof(*src_buffer);
- unsigned dest_frames =
- (src_frames * dest_rate + src_rate - 1) / src_rate;
- unsigned dest_samples = dest_frames * channels;
- size_t dest_size = dest_samples * sizeof(*src_buffer);
- int32_t *dest_buffer = (int32_t *)state->buffer.Get(dest_size);
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- switch (channels) {
- case 1:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- }
- break;
- case 2:
- while (dest_pos < dest_samples) {
- unsigned src_pos = dest_pos * src_rate / dest_rate;
- src_pos &= ~1;
-
- dest_buffer[dest_pos++] = src_buffer[src_pos];
- dest_buffer[dest_pos++] = src_buffer[src_pos + 1];
- }
- break;
- }
-
- *dest_size_r = dest_size;
- return dest_buffer;
-}
diff --git a/src/pcm/PcmResampleInternal.hxx b/src/pcm/PcmResampleInternal.hxx
deleted file mode 100644
index 5090c13d1..000000000
--- a/src/pcm/PcmResampleInternal.hxx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * Internal declarations for the pcm_resample library. The "internal"
- * resampler is called "fallback" in the MPD source, so the file name
- * of this header is somewhat unrelated to it.
- */
-
-#ifndef MPD_PCM_RESAMPLE_INTERNAL_HXX
-#define MPD_PCM_RESAMPLE_INTERNAL_HXX
-
-#include "check.h"
-#include "PcmResample.hxx"
-
-#ifdef HAVE_LIBSAMPLERATE
-
-bool
-pcm_resample_lsr_global_init(const char *converter, Error &error);
-
-void
-pcm_resample_lsr_init(PcmResampler *state);
-
-void
-pcm_resample_lsr_deinit(PcmResampler *state);
-
-void
-pcm_resample_lsr_reset(PcmResampler *state);
-
-const float *
-pcm_resample_lsr_float(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-const int16_t *
-pcm_resample_lsr_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-const int32_t *
-pcm_resample_lsr_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-const int32_t *
-pcm_resample_lsr_24(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error);
-
-#endif
-
-const int16_t *
-pcm_resample_fallback_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
-
-const int32_t *
-pcm_resample_fallback_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer,
- size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
-
-#endif
diff --git a/src/pcm/PcmResampleLibsamplerate.cxx b/src/pcm/PcmResampleLibsamplerate.cxx
deleted file mode 100644
index 9eac2d545..000000000
--- a/src/pcm/PcmResampleLibsamplerate.cxx
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PcmResampleInternal.hxx"
-#include "PcmUtils.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-static int lsr_converter = SRC_SINC_FASTEST;
-
-static constexpr Domain libsamplerate_domain("libsamplerate");
-
-static bool
-lsr_parse_converter(const char *s)
-{
- assert(s != nullptr);
-
- if (*s == 0)
- return true;
-
- char *endptr;
- long l = strtol(s, &endptr, 10);
- if (*endptr == 0 && src_get_name(l) != nullptr) {
- lsr_converter = l;
- return true;
- }
-
- size_t length = strlen(s);
- for (int i = 0;; ++i) {
- const char *name = src_get_name(i);
- if (name == nullptr)
- break;
-
- if (StringEqualsCaseASCII(s, name, length)) {
- lsr_converter = i;
- return true;
- }
- }
-
- return false;
-}
-
-bool
-pcm_resample_lsr_global_init(const char *converter, Error &error)
-{
- if (!lsr_parse_converter(converter)) {
- error.Format(libsamplerate_domain,
- "unknown samplerate converter '%s'", converter);
- return false;
- }
-
- FormatDebug(libsamplerate_domain,
- "libsamplerate converter '%s'",
- src_get_name(lsr_converter));
-
- return true;
-}
-
-void
-pcm_resample_lsr_init(PcmResampler *state)
-{
- state->state = nullptr;
- memset(&state->data, 0, sizeof(state->data));
- memset(&state->prev, 0, sizeof(state->prev));
- state->error = 0;
-}
-
-void
-pcm_resample_lsr_deinit(PcmResampler *state)
-{
- if (state->state != nullptr)
- state->state = src_delete(state->state);
-}
-
-void
-pcm_resample_lsr_reset(PcmResampler *state)
-{
- if (state->state != nullptr)
- src_reset(state->state);
-}
-
-static bool
-pcm_resample_set(PcmResampler *state,
- unsigned channels, unsigned src_rate, unsigned dest_rate,
- Error &error_r)
-{
- /* (re)set the state/ratio if the in or out format changed */
- if (channels == state->prev.channels &&
- src_rate == state->prev.src_rate &&
- dest_rate == state->prev.dest_rate)
- return true;
-
- state->error = 0;
- state->prev.channels = channels;
- state->prev.src_rate = src_rate;
- state->prev.dest_rate = dest_rate;
-
- if (state->state)
- state->state = src_delete(state->state);
-
- int error;
- state->state = src_new(lsr_converter, channels, &error);
- if (!state->state) {
- error_r.Format(libsamplerate_domain, error,
- "libsamplerate initialization has failed: %s",
- src_strerror(error));
- return false;
- }
-
- SRC_DATA *data = &state->data;
- data->src_ratio = (double)dest_rate / (double)src_rate;
- FormatDebug(libsamplerate_domain,
- "setting samplerate conversion ratio to %.2lf",
- data->src_ratio);
- src_set_ratio(state->state, data->src_ratio);
-
- return true;
-}
-
-static bool
-lsr_process(PcmResampler *state, Error &error)
-{
- if (state->error == 0)
- state->error = src_process(state->state, &state->data);
- if (state->error) {
- error.Format(libsamplerate_domain, state->error,
- "libsamplerate has failed: %s",
- src_strerror(state->error));
- return false;
- }
-
- return true;
-}
-
-const float *
-pcm_resample_lsr_float(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const float *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- SRC_DATA *data = &state->data;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate, error))
- return nullptr;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- data->data_in = const_cast<float *>(src_buffer);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- size_t data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = (float *)state->out.Get(data_out_size);
-
- if (!lsr_process(state, error))
- return nullptr;
-
- *dest_size_r = data->output_frames_gen *
- sizeof(*data->data_out) * channels;
- return data->data_out;
-}
-
-const int16_t *
-pcm_resample_lsr_16(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- SRC_DATA *data = &state->data;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate,
- error))
- return nullptr;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- size_t data_in_size = data->input_frames * sizeof(float) * channels;
- data->data_in = (float *)state->in.Get(data_in_size);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- size_t data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = (float *)state->out.Get(data_out_size);
-
- src_short_to_float_array(src_buffer, data->data_in,
- data->input_frames * channels);
-
- if (!lsr_process(state, error))
- return nullptr;
-
- int16_t *dest_buffer;
- *dest_size_r = data->output_frames_gen *
- sizeof(*dest_buffer) * channels;
- dest_buffer = (int16_t *)state->buffer.Get(*dest_size_r);
- src_float_to_short_array(data->data_out, dest_buffer,
- data->output_frames_gen * channels);
-
- return dest_buffer;
-}
-
-#ifdef HAVE_LIBSAMPLERATE_NOINT
-
-/* libsamplerate introduced these functions in v0.1.3 */
-
-static void
-src_int_to_float_array(const int *in, float *out, int len)
-{
- while (len-- > 0)
- *out++ = *in++ / (float)(1 << (24 - 1));
-}
-
-static void
-src_float_to_int_array (const float *in, int *out, int len)
-{
- while (len-- > 0)
- *out++ = *in++ * (float)(1 << (24 - 1));
-}
-
-#endif
-
-const int32_t *
-pcm_resample_lsr_32(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- SRC_DATA *data = &state->data;
-
- assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
-
- if (!pcm_resample_set(state, channels, src_rate, dest_rate,
- error))
- return nullptr;
-
- data->input_frames = src_size / sizeof(*src_buffer) / channels;
- size_t data_in_size = data->input_frames * sizeof(float) * channels;
- data->data_in = (float *)state->in.Get(data_in_size);
-
- data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
- size_t data_out_size = data->output_frames * sizeof(float) * channels;
- data->data_out = (float *)state->out.Get(data_out_size);
-
- src_int_to_float_array(src_buffer, data->data_in,
- data->input_frames * channels);
-
- if (!lsr_process(state, error))
- return nullptr;
-
- int32_t *dest_buffer;
- *dest_size_r = data->output_frames_gen *
- sizeof(*dest_buffer) * channels;
- dest_buffer = (int32_t *)state->buffer.Get(*dest_size_r);
- src_float_to_int_array(data->data_out, dest_buffer,
- data->output_frames_gen * channels);
-
- return dest_buffer;
-}
-
-const int32_t *
-pcm_resample_lsr_24(PcmResampler *state,
- unsigned channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate, size_t *dest_size_r,
- Error &error)
-{
- const auto result = pcm_resample_lsr_32(state, channels,
- src_rate, src_buffer, src_size,
- dest_rate, dest_size_r,
- error);
- if (result != nullptr)
- /* src_float_to_int_array() clamps for 32 bit
- integers; now make sure everything's fine for 24
- bit */
- /* TODO: eliminate the 32 bit clamp to reduce overhead */
- PcmClampN<int32_t, int32_t, 24>(const_cast<int32_t *>(result),
- result,
- *dest_size_r / sizeof(*result));
-
- return result;
-}
diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx
index febe12d7b..23870a729 100644
--- a/src/pcm/PcmUtils.hxx
+++ b/src/pcm/PcmUtils.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,53 +26,31 @@
#include <stdint.h>
-/**
- * Add a byte count to the specified pointer. This is a utility
- * function to convert a source pointer and a byte count to an "end"
- * pointer for use in loops.
- */
-template<typename T>
-static inline const T *
-pcm_end_pointer(const T *p, size_t size)
-{
- return (const T *)((const uint8_t *)p + size);
-}
+enum class SampleFormat : uint8_t;
+template<SampleFormat F> struct SampleTraits;
/**
* Check if the value is within the range of the provided bit size,
* and caps it if necessary.
*/
-template<typename T, typename U, unsigned bits>
+template<SampleFormat F, class Traits=SampleTraits<F>>
gcc_const
-static inline T
-PcmClamp(U x)
+static inline typename Traits::value_type
+PcmClamp(typename Traits::long_type x)
{
- constexpr U MIN_VALUE = -(U(1) << (bits - 1));
- constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1;
+ typedef typename Traits::value_type T;
typedef std::numeric_limits<T> limits;
- static_assert(MIN_VALUE >= limits::min(), "out of range");
- static_assert(MAX_VALUE <= limits::max(), "out of range");
+ static_assert(Traits::MIN >= limits::min(), "out of range");
+ static_assert(Traits::MAX <= limits::max(), "out of range");
- if (gcc_unlikely(x < MIN_VALUE))
- return T(MIN_VALUE);
+ if (gcc_unlikely(x < Traits::MIN))
+ return T(Traits::MIN);
- if (gcc_unlikely(x > MAX_VALUE))
- return T(MAX_VALUE);
+ if (gcc_unlikely(x > Traits::MAX))
+ return T(Traits::MAX);
return T(x);
}
-/**
- * Check if the values in this buffer are within the range of the
- * provided bit size, and clamps them whenever necessary.
- */
-template<typename T, typename U, unsigned bits>
-static inline void
-PcmClampN(T *dest, const U *src, unsigned n)
-{
- while (n-- > 0)
- *dest++ = PcmClamp<T, U, bits>(*src++);
-}
-
#endif
diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx
deleted file mode 100644
index 564880633..000000000
--- a/src/pcm/PcmVolume.cxx
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PcmVolume.hxx"
-#include "PcmUtils.hxx"
-#include "AudioFormat.hxx"
-
-#include <stdint.h>
-#include <string.h>
-
-static void
-pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume)
-{
- while (buffer < end) {
- int32_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer++ = PcmClamp<int8_t, int16_t, 8>(sample);
- }
-}
-
-static void
-pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume)
-{
- while (buffer < end) {
- int32_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-
- *buffer++ = PcmClamp<int16_t, int32_t, 16>(sample);
- }
-}
-
-#ifdef __i386__
-/**
- * Optimized volume function for i386. Use the EDX:EAX 2*32 bit
- * multiplication result instead of emulating 64 bit multiplication.
- */
-static inline int32_t
-pcm_volume_sample_24(int32_t sample, int32_t volume, gcc_unused int32_t dither)
-{
- int32_t result;
-
- asm(/* edx:eax = sample * volume */
- "imul %2\n"
-
- /* "add %3, %1\n" dithering disabled for now, because we
- have no overflow check - is dithering really important
- here? */
-
- /* eax = edx:eax / PCM_VOLUME_1 */
- "sal $22, %%edx\n"
- "shr $10, %1\n"
- "or %%edx, %1\n"
-
- : "=a"(result)
- : "0"(sample), "r"(volume) /* , "r"(dither) */
- : "edx"
- );
-
- return result;
-}
-#endif
-
-static void
-pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume)
-{
- while (buffer < end) {
-#ifdef __i386__
- /* assembly version for i386 */
- int32_t sample = *buffer;
-
- sample = pcm_volume_sample_24(sample, volume,
- pcm_volume_dither());
-#else
- /* portable version */
- int64_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
-#endif
- *buffer++ = PcmClamp<int32_t, int32_t, 24>(sample);
- }
-}
-
-static void
-pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume)
-{
- while (buffer < end) {
-#ifdef __i386__
- /* assembly version for i386 */
- int32_t sample = *buffer;
-
- *buffer++ = pcm_volume_sample_24(sample, volume, 0);
-#else
- /* portable version */
- int64_t sample = *buffer;
-
- sample = (sample * volume + pcm_volume_dither() +
- PCM_VOLUME_1 / 2)
- / PCM_VOLUME_1;
- *buffer++ = PcmClamp<int32_t, int64_t, 32>(sample);
-#endif
- }
-}
-
-static void
-pcm_volume_change_float(float *buffer, const float *end, float volume)
-{
- while (buffer < end) {
- float sample = *buffer;
- sample *= volume;
- *buffer++ = sample;
- }
-}
-
-bool
-pcm_volume(void *buffer, size_t length,
- SampleFormat format,
- int volume)
-{
- if (volume == PCM_VOLUME_1)
- return true;
-
- if (volume <= 0) {
- memset(buffer, 0, length);
- return true;
- }
-
- const void *end = pcm_end_pointer(buffer, length);
- switch (format) {
- case SampleFormat::UNDEFINED:
- case SampleFormat::DSD:
- /* not implemented */
- return false;
-
- case SampleFormat::S8:
- pcm_volume_change_8((int8_t *)buffer, (const int8_t *)end,
- volume);
- return true;
-
- case SampleFormat::S16:
- pcm_volume_change_16((int16_t *)buffer, (const int16_t *)end,
- volume);
- return true;
-
- case SampleFormat::S24_P32:
- pcm_volume_change_24((int32_t *)buffer, (const int32_t *)end,
- volume);
- return true;
-
- case SampleFormat::S32:
- pcm_volume_change_32((int32_t *)buffer, (const int32_t *)end,
- volume);
- return true;
-
- case SampleFormat::FLOAT:
- pcm_volume_change_float((float *)buffer, (const float *)end,
- pcm_volume_to_float(volume));
- return true;
- }
-
- assert(false);
- gcc_unreachable();
-}
diff --git a/src/pcm/PcmVolume.hxx b/src/pcm/PcmVolume.hxx
deleted file mode 100644
index 8cd82acf7..000000000
--- a/src/pcm/PcmVolume.hxx
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PCM_VOLUME_HXX
-#define MPD_PCM_VOLUME_HXX
-
-#include "PcmPrng.hxx"
-#include "AudioFormat.hxx"
-
-#include <stdint.h>
-#include <stddef.h>
-
-enum {
- /** this value means "100% volume" */
- PCM_VOLUME_1 = 1024,
-};
-
-struct AudioFormat;
-
-/**
- * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an
- * integer volume value (1000 = 100%).
- */
-static inline int
-pcm_float_to_volume(float volume)
-{
- return volume * PCM_VOLUME_1 + 0.5;
-}
-
-static inline float
-pcm_volume_to_float(int volume)
-{
- return (float)volume / (float)PCM_VOLUME_1;
-}
-
-/**
- * Returns the next volume dithering number, between -511 and +511.
- * This number is taken from a global PRNG, see pcm_prng().
- */
-static inline int
-pcm_volume_dither(void)
-{
- static unsigned long state;
- uint32_t r;
-
- r = state = pcm_prng(state);
-
- return (r & 511) - ((r >> 9) & 511);
-}
-
-/**
- * Adjust the volume of the specified PCM buffer.
- *
- * @param buffer the PCM buffer
- * @param length the length of the PCM buffer
- * @param format the sample format of the PCM buffer
- * @param volume the volume between 0 and #PCM_VOLUME_1
- * @return true on success, false if the audio format is not supported
- */
-bool
-pcm_volume(void *buffer, size_t length,
- SampleFormat format,
- int volume);
-
-#endif
diff --git a/src/pcm/Resampler.hxx b/src/pcm/Resampler.hxx
new file mode 100644
index 000000000..9b6ccbbc7
--- /dev/null
+++ b/src/pcm/Resampler.hxx
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_RESAMPLER_HXX
+#define MPD_PCM_RESAMPLER_HXX
+
+#include "util/ConstBuffer.hxx"
+#include "Compiler.h"
+
+struct AudioFormat;
+class Error;
+
+/**
+ * This is an interface for plugins that convert PCM data to a
+ * specific sample rate.
+ */
+class PcmResampler {
+public:
+ virtual ~PcmResampler() {}
+
+ /**
+ * Opens the resampler, preparing it for Resample().
+ *
+ * @param af the audio format of incoming data; the plugin may
+ * modify the object to enforce another input format (however,
+ * it may not request a different input sample rate)
+ * @param new_sample_rate the requested output sample rate
+ * @param error location to store the error
+ * @return the format of outgoing data or
+ * AudioFormat::Undefined() on error
+ */
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) = 0;
+
+ /**
+ * Closes the resampler. After that, you may call Open()
+ * again.
+ */
+ virtual void Close() = 0;
+
+ /**
+ * Resamples a block of PCM data.
+ *
+ * @param src the input buffer
+ * @param src_size the size of #src_buffer in bytes
+ * @param dest_size_r the size of the returned buffer
+ * @param error location to store the error occurring, or nullptr
+ * to ignore errors.
+ * @return the destination buffer on success (will be
+ * invalidated by filter_close() or filter_filter()), nullptr on
+ * error
+ */
+ gcc_pure
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) = 0;
+};
+
+#endif
diff --git a/src/pcm/ShiftConvert.hxx b/src/pcm/ShiftConvert.hxx
new file mode 100644
index 000000000..92f96b7ba
--- /dev/null
+++ b/src/pcm/ShiftConvert.hxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_SHIFT_CONVERT_HXX
+#define MPD_PCM_SHIFT_CONVERT_HXX
+
+#include "Traits.hxx"
+
+/**
+ * Convert from one integer sample format to another by shifting bits
+ * to the left.
+ */
+template<SampleFormat SF, SampleFormat DF,
+ class ST=SampleTraits<SF>,
+ class DT=SampleTraits<DF>>
+struct LeftShiftSampleConvert {
+ typedef ST SrcTraits;
+ typedef DT DstTraits;
+
+ typedef typename SrcTraits::value_type SV;
+ typedef typename DstTraits::value_type DV;
+
+ static_assert(SrcTraits::BITS < DstTraits::BITS,
+ "Source format must be smaller than destination format");
+
+ constexpr static DV Convert(SV src) {
+ return DV(src) << (DstTraits::BITS - SrcTraits::BITS);
+ }
+};
+
+/**
+ * Convert from one integer sample format to another by shifting bits
+ * to the right.
+ */
+template<SampleFormat SF, SampleFormat DF,
+ class ST=SampleTraits<SF>,
+ class DT=SampleTraits<DF>>
+struct RightShiftSampleConvert {
+ typedef ST SrcTraits;
+ typedef DT DstTraits;
+
+ typedef typename SrcTraits::value_type SV;
+ typedef typename DstTraits::value_type DV;
+
+ static_assert(SrcTraits::BITS > DstTraits::BITS,
+ "Source format must be smaller than destination format");
+
+ constexpr static DV Convert(SV src) {
+ return src >> (SrcTraits::BITS - DstTraits::BITS);
+ }
+};
+
+#endif
diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx
new file mode 100644
index 000000000..b9d6fc099
--- /dev/null
+++ b/src/pcm/SoxrResampler.cxx
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SoxrResampler.hxx"
+#include "AudioFormat.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <soxr.h>
+
+#include <assert.h>
+#include <string.h>
+
+static constexpr Domain soxr_domain("soxr");
+
+static unsigned long soxr_quality_recipe = SOXR_HQ;
+
+static const char *
+soxr_quality_name(unsigned long recipe)
+{
+ switch (recipe) {
+ case SOXR_VHQ:
+ return "Very High Quality";
+ case SOXR_HQ:
+ return "High Quality";
+ case SOXR_MQ:
+ return "Medium Quality";
+ case SOXR_LQ:
+ return "Low Quality";
+ case SOXR_QQ:
+ return "Quick";
+ }
+
+ gcc_unreachable();
+}
+
+static bool
+soxr_parse_converter(const char *converter)
+{
+ assert(converter != nullptr);
+
+ assert(memcmp(converter, "soxr", 4) == 0);
+ if (converter[4] == '\0')
+ return true;
+ if (converter[4] != ' ')
+ return false;
+
+ // converter example is "soxr very high", we want the "very high" part
+ const char *quality = converter + 5;
+ if (strcmp(quality, "very high") == 0)
+ soxr_quality_recipe = SOXR_VHQ;
+ else if (strcmp(quality, "high") == 0)
+ soxr_quality_recipe = SOXR_HQ;
+ else if (strcmp(quality, "medium") == 0)
+ soxr_quality_recipe = SOXR_MQ;
+ else if (strcmp(quality, "low") == 0)
+ soxr_quality_recipe = SOXR_LQ;
+ else if (strcmp(quality, "quick") == 0)
+ soxr_quality_recipe = SOXR_QQ;
+ else
+ return false;
+
+ return true;
+}
+
+bool
+pcm_resample_soxr_global_init(const char *converter, Error &error)
+{
+ if (!soxr_parse_converter(converter)) {
+ error.Format(soxr_domain,
+ "unknown samplerate converter '%s'", converter);
+ return false;
+ }
+
+ FormatDebug(soxr_domain,
+ "soxr converter '%s'",
+ soxr_quality_name(soxr_quality_recipe));
+
+ return true;
+}
+
+AudioFormat
+SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error)
+{
+ assert(af.IsValid());
+ assert(audio_valid_sample_rate(new_sample_rate));
+
+ soxr_error_t e;
+ soxr_quality_spec_t quality = soxr_quality_spec(soxr_quality_recipe, 0);
+ soxr = soxr_create(af.sample_rate, new_sample_rate,
+ af.channels, &e,
+ nullptr, &quality, nullptr);
+ if (soxr == nullptr) {
+ error.Format(soxr_domain,
+ "soxr initialization has failed: %s", e);
+ return AudioFormat::Undefined();
+ }
+
+ FormatDebug(soxr_domain, "soxr engine '%s'", soxr_engine(soxr));
+
+ channels = af.channels;
+
+ ratio = float(new_sample_rate) / float(af.sample_rate);
+ FormatDebug(soxr_domain,
+ "samplerate conversion ratio to %.2lf",
+ ratio);
+
+ /* libsoxr works with floating point samples */
+ af.format = SampleFormat::FLOAT;
+
+ AudioFormat result = af;
+ result.sample_rate = new_sample_rate;
+ return result;
+}
+
+void
+SoxrPcmResampler::Close()
+{
+ soxr_delete(soxr);
+}
+
+ConstBuffer<void>
+SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error)
+{
+ const size_t frame_size = channels * sizeof(float);
+ assert(src.size % frame_size == 0);
+
+ const size_t n_frames = src.size / frame_size;
+
+ /* always round up: worst case output buffer size */
+ const size_t o_frames = size_t(n_frames * ratio) + 1;
+
+ float *output_buffer = (float *)buffer.Get(o_frames * frame_size);
+
+ size_t i_done, o_done;
+ soxr_error_t e = soxr_process(soxr, src.data, n_frames, &i_done,
+ output_buffer, o_frames, &o_done);
+ if (e != nullptr) {
+ error.Format(soxr_domain, "soxr error: %s", e);
+ return nullptr;
+ }
+
+ return { output_buffer, o_done * frame_size };
+}
diff --git a/src/pcm/SoxrResampler.hxx b/src/pcm/SoxrResampler.hxx
new file mode 100644
index 000000000..e4cba4a64
--- /dev/null
+++ b/src/pcm/SoxrResampler.hxx
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_SOXR_RESAMPLER_HXX
+#define MPD_PCM_SOXR_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "Compiler.h"
+
+struct AudioFormat;
+
+/**
+ * A resampler using soxr.
+ */
+class SoxrPcmResampler final : public PcmResampler {
+ struct soxr *soxr;
+
+ unsigned channels;
+ float ratio;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+bool
+pcm_resample_soxr_global_init(const char *converter, Error &error);
+
+#endif
diff --git a/src/pcm/Traits.hxx b/src/pcm/Traits.hxx
new file mode 100644
index 000000000..97259ac73
--- /dev/null
+++ b/src/pcm/Traits.hxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_TRAITS_HXX
+#define MPD_PCM_TRAITS_HXX
+
+#include "check.h"
+#include "AudioFormat.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+/**
+ * This template describes the specified #SampleFormat. This is an
+ * empty prototype; the specializations contain the real definitions.
+ * See SampleTraits<uint8_t> for more documentation.
+ */
+template<SampleFormat F>
+struct SampleTraits {};
+
+template<>
+struct SampleTraits<SampleFormat::S8> {
+ /**
+ * The type used for one sample value.
+ */
+ typedef int8_t value_type;
+
+ /**
+ * A writable pointer.
+ */
+ typedef value_type *pointer_type;
+
+ /**
+ * A read-only pointer.
+ */
+ typedef const value_type *const_pointer_type;
+
+ /**
+ * A "long" type that is large and accurate enough for adding
+ * two values without risking an (integer) overflow or
+ * (floating point) precision loss.
+ */
+ typedef int sum_type;
+
+ /**
+ * A "long" type that is large and accurate enough for
+ * arithmetic without risking an (integer) overflow or
+ * (floating point) precision loss.
+ */
+ typedef int_least32_t long_type;
+
+ /**
+ * The size of one sample in bytes.
+ */
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+
+ /**
+ * The integer bit depth of one sample. This attribute may
+ * not exist if this is not an integer sample format.
+ */
+ static constexpr unsigned BITS = sizeof(value_type) * 8;
+
+ /**
+ * The minimum sample value.
+ */
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+
+ /**
+ * The maximum sample value.
+ */
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::S16> {
+ typedef int16_t value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef int_least32_t sum_type;
+ typedef int_least32_t long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+ static constexpr unsigned BITS = sizeof(value_type) * 8;
+
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::S32> {
+ typedef int32_t value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef int_least64_t sum_type;
+ typedef int_least64_t long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+ static constexpr unsigned BITS = sizeof(value_type) * 8;
+
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::S24_P32> {
+ typedef int32_t value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef int_least32_t sum_type;
+ typedef int_least64_t long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+ static constexpr unsigned BITS = 24;
+
+ static constexpr value_type MIN = -(sum_type(1) << (BITS - 1));
+ static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1;
+};
+
+template<>
+struct SampleTraits<SampleFormat::FLOAT> {
+ typedef float value_type;
+ typedef value_type *pointer_type;
+ typedef const value_type *const_pointer_type;
+
+ typedef float sum_type;
+ typedef float long_type;
+
+ static constexpr size_t SAMPLE_SIZE = sizeof(value_type);
+
+ static constexpr value_type MIN = -1;
+ static constexpr value_type MAX = 1;
+};
+
+#endif
diff --git a/src/pcm/Volume.cxx b/src/pcm/Volume.cxx
new file mode 100644
index 000000000..b12d8fd41
--- /dev/null
+++ b/src/pcm/Volume.cxx
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Volume.hxx"
+#include "Domain.hxx"
+#include "PcmUtils.hxx"
+#include "Traits.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include "PcmDither.cxx" // including the .cxx file to get inlined templates
+
+#include <stdint.h>
+#include <string.h>
+
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static inline typename Traits::value_type
+pcm_volume_sample(PcmDither &dither,
+ typename Traits::value_type _sample,
+ int volume)
+{
+ typename Traits::long_type sample(_sample);
+
+ return dither.DitherShift<typename Traits::long_type,
+ Traits::BITS + PCM_VOLUME_BITS,
+ Traits::BITS>(sample * volume);
+}
+
+template<SampleFormat F, class Traits=SampleTraits<F>>
+static void
+pcm_volume_change(PcmDither &dither,
+ typename Traits::pointer_type dest,
+ typename Traits::const_pointer_type src,
+ size_t n,
+ int volume)
+{
+ for (size_t i = 0; i != n; ++i)
+ dest[i] = pcm_volume_sample<F, Traits>(dither, src[i], volume);
+}
+
+static void
+pcm_volume_change_8(PcmDither &dither,
+ int8_t *dest, const int8_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S8>(dither, dest, src, n, volume);
+}
+
+static void
+pcm_volume_change_16(PcmDither &dither,
+ int16_t *dest, const int16_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S16>(dither, dest, src, n, volume);
+}
+
+static void
+pcm_volume_change_24(PcmDither &dither,
+ int32_t *dest, const int32_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S24_P32>(dither, dest, src, n,
+ volume);
+}
+
+static void
+pcm_volume_change_32(PcmDither &dither,
+ int32_t *dest, const int32_t *src, size_t n,
+ int volume)
+{
+ pcm_volume_change<SampleFormat::S32>(dither, dest, src, n, volume);
+}
+
+static void
+pcm_volume_change_float(float *dest, const float *src, size_t n,
+ float volume)
+{
+ for (size_t i = 0; i != n; ++i)
+ dest[i] = src[i] * volume;
+}
+
+bool
+PcmVolume::Open(SampleFormat _format, Error &error)
+{
+ assert(format == SampleFormat::UNDEFINED);
+
+ switch (_format) {
+ case SampleFormat::UNDEFINED:
+ error.Format(pcm_domain,
+ "Software volume for %s is not implemented",
+ sample_format_to_string(_format));
+ return false;
+
+ case SampleFormat::S8:
+ case SampleFormat::S16:
+ case SampleFormat::S24_P32:
+ case SampleFormat::S32:
+ case SampleFormat::FLOAT:
+ break;
+
+ case SampleFormat::DSD:
+ // TODO: implement this; currently, it's a no-op
+ break;
+ }
+
+ format = _format;
+ return true;
+}
+
+ConstBuffer<void>
+PcmVolume::Apply(ConstBuffer<void> src)
+{
+ if (volume == PCM_VOLUME_1)
+ return src;
+
+ void *data = buffer.Get(src.size);
+
+ if (volume == 0) {
+ /* optimized special case: 0% volume = memset(0) */
+ /* TODO: is this valid for all sample formats? What
+ about floating point? */
+ memset(data, 0, src.size);
+ return { data, src.size };
+ }
+
+ switch (format) {
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ pcm_volume_change_8(dither, (int8_t *)data,
+ (const int8_t *)src.data,
+ src.size / sizeof(int8_t),
+ volume);
+ break;
+
+ case SampleFormat::S16:
+ pcm_volume_change_16(dither, (int16_t *)data,
+ (const int16_t *)src.data,
+ src.size / sizeof(int16_t),
+ volume);
+ break;
+
+ case SampleFormat::S24_P32:
+ pcm_volume_change_24(dither, (int32_t *)data,
+ (const int32_t *)src.data,
+ src.size / sizeof(int32_t),
+ volume);
+ break;
+
+ case SampleFormat::S32:
+ pcm_volume_change_32(dither, (int32_t *)data,
+ (const int32_t *)src.data,
+ src.size / sizeof(int32_t),
+ volume);
+ break;
+
+ case SampleFormat::FLOAT:
+ pcm_volume_change_float((float *)data,
+ (const float *)src.data,
+ src.size / sizeof(float),
+ pcm_volume_to_float(volume));
+ break;
+
+ case SampleFormat::DSD:
+ // TODO: implement this; currently, it's a no-op
+ return src;
+ }
+
+ return { data, src.size };
+}
diff --git a/src/pcm/Volume.hxx b/src/pcm/Volume.hxx
new file mode 100644
index 000000000..a156fc72e
--- /dev/null
+++ b/src/pcm/Volume.hxx
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_VOLUME_HXX
+#define MPD_PCM_VOLUME_HXX
+
+#include "AudioFormat.hxx"
+#include "PcmBuffer.hxx"
+#include "PcmDither.hxx"
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+class Error;
+template<typename T> struct ConstBuffer;
+
+/**
+ * Number of fractional bits for a fixed-point volume value.
+ */
+static constexpr unsigned PCM_VOLUME_BITS = 10;
+
+/**
+ * This value means "100% volume".
+ */
+static constexpr unsigned PCM_VOLUME_1 = 1024;
+static constexpr int PCM_VOLUME_1S = PCM_VOLUME_1;
+
+struct AudioFormat;
+
+/**
+ * Converts a float value (0.0 = silence, 1.0 = 100% volume) to an
+ * integer volume value (1000 = 100%).
+ */
+static inline int
+pcm_float_to_volume(float volume)
+{
+ return volume * PCM_VOLUME_1 + 0.5;
+}
+
+static inline float
+pcm_volume_to_float(int volume)
+{
+ return (float)volume / (float)PCM_VOLUME_1;
+}
+
+/**
+ * A class that converts samples from one format to another.
+ */
+class PcmVolume {
+ SampleFormat format;
+
+ unsigned volume;
+
+ PcmBuffer buffer;
+ PcmDither dither;
+
+public:
+ PcmVolume()
+ :volume(PCM_VOLUME_1) {
+#ifndef NDEBUG
+ format = SampleFormat::UNDEFINED;
+#endif
+ }
+
+#ifndef NDEBUG
+ ~PcmVolume() {
+ assert(format == SampleFormat::UNDEFINED);
+ }
+#endif
+
+ unsigned GetVolume() const {
+ return volume;
+ }
+
+ /**
+ * @param _volume the volume level in the range
+ * [0..#PCM_VOLUME_1]; may be bigger than #PCM_VOLUME_1, but
+ * then it will most likely clip a lot
+ */
+ void SetVolume(unsigned _volume) {
+ volume = _volume;
+ }
+
+ /**
+ * Opens the object, prepare for Apply().
+ *
+ * @param format the sample format
+ * @param error location to store the error
+ * @return true on success
+ */
+ bool Open(SampleFormat format, Error &error);
+
+ /**
+ * Closes the object. After that, you may call Open() again.
+ */
+ void Close() {
+#ifndef NDEBUG
+ assert(format != SampleFormat::UNDEFINED);
+ format = SampleFormat::UNDEFINED;
+#endif
+ }
+
+ /**
+ * Apply the volume level.
+ */
+ gcc_pure
+ ConstBuffer<void> Apply(ConstBuffer<void> src);
+};
+
+#endif
diff --git a/src/pcm/dsd2pcm/dsd2pcm.c b/src/pcm/dsd2pcm/dsd2pcm.c
index 4c7640853..f089102ef 100644
--- a/src/pcm/dsd2pcm/dsd2pcm.c
+++ b/src/pcm/dsd2pcm/dsd2pcm.c
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#include "util/bit_reverse.h"
#include <stdlib.h>
diff --git a/src/pcm/dsd2pcm/dsd2pcm.h b/src/pcm/dsd2pcm/dsd2pcm.h
index 80e8ce0cc..df1f4a33d 100644
--- a/src/pcm/dsd2pcm/dsd2pcm.h
+++ b/src/pcm/dsd2pcm/dsd2pcm.h
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#ifndef DSD2PCM_H_INCLUDED
#define DSD2PCM_H_INCLUDED
diff --git a/src/pcm/dsd2pcm/dsd2pcm.hpp b/src/pcm/dsd2pcm/dsd2pcm.hpp
index 8f3f55197..3799dfab2 100644
--- a/src/pcm/dsd2pcm/dsd2pcm.hpp
+++ b/src/pcm/dsd2pcm/dsd2pcm.hpp
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#ifndef DSD2PCM_HXX_INCLUDED
#define DSD2PCM_HXX_INCLUDED
diff --git a/src/pcm/dsd2pcm/main.cpp b/src/pcm/dsd2pcm/main.cpp
index 0b58888a8..7a3082e53 100644
--- a/src/pcm/dsd2pcm/main.cpp
+++ b/src/pcm/dsd2pcm/main.cpp
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#include <iostream>
#include <vector>
#include <cstring>
diff --git a/src/pcm/dsd2pcm/noiseshape.c b/src/pcm/dsd2pcm/noiseshape.c
index ecd2f251d..cb3850238 100644
--- a/src/pcm/dsd2pcm/noiseshape.c
+++ b/src/pcm/dsd2pcm/noiseshape.c
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#include <stdlib.h>
#include <string.h>
diff --git a/src/pcm/dsd2pcm/noiseshape.h b/src/pcm/dsd2pcm/noiseshape.h
index 6075f0d88..7818c1d67 100644
--- a/src/pcm/dsd2pcm/noiseshape.h
+++ b/src/pcm/dsd2pcm/noiseshape.h
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#ifndef NOISE_SHAPE_H_INCLUDED
#define NOISE_SHAPE_H_INCLUDED
diff --git a/src/pcm/dsd2pcm/noiseshape.hpp b/src/pcm/dsd2pcm/noiseshape.hpp
index 1fc698b36..58515b158 100644
--- a/src/pcm/dsd2pcm/noiseshape.hpp
+++ b/src/pcm/dsd2pcm/noiseshape.hpp
@@ -1,3 +1,33 @@
+/*
+
+Copyright 2009, 2011 Sebastian Gesemann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. 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 SEBASTIAN GESEMANN ''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 SEBASTIAN GESEMANN 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.
+
+The views and conclusions contained in the software and documentation are those of the
+authors and should not be interpreted as representing official policies, either expressed
+or implied, of Sebastian Gesemann.
+
+ */
+
#ifndef NOISE_SHAPE_HXX_INCLUDED
#define NOISE_SHAPE_HXX_INCLUDED
diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx
deleted file mode 100644
index 94198b8c3..000000000
--- a/src/playlist/AsxPlaylistPlugin.cxx
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "AsxPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain asx_domain("asx");
-
-/**
- * This is the state object for the GLib XML parser.
- */
-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.
- */
- TagType 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] != nullptr; ++i)
- if (StringEqualsCaseASCII(attribute_names[i], name))
- return attribute_values[i];
-
- return nullptr;
-}
-
-static void
-asx_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
-{
- AsxParser *parser = (AsxParser *)user_data;
-
- switch (parser->state) {
- case AsxParser::ROOT:
- if (StringEqualsCaseASCII(element_name, "entry")) {
- parser->state = AsxParser::ENTRY;
- parser->song = Song::NewRemote("asx:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case AsxParser::ENTRY:
- if (StringEqualsCaseASCII(element_name, "ref")) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "href");
- if (href != nullptr) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- Song *song = Song::NewRemote(href);
-
- if (parser->song != nullptr) {
- song->tag = parser->song->tag;
- parser->song->tag = nullptr;
- parser->song->Free();
- }
-
- parser->song = song;
- }
- } else if (StringEqualsCaseASCII(element_name, "author"))
- /* is that correct? or should it be COMPOSER
- or PERFORMER? */
- parser->tag = TAG_ARTIST;
- else if (StringEqualsCaseASCII(element_name, "title"))
- parser->tag = TAG_TITLE;
-
- break;
- }
-}
-
-static void
-asx_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
-{
- AsxParser *parser = (AsxParser *)user_data;
-
- switch (parser->state) {
- case AsxParser::ROOT:
- break;
-
- case AsxParser::ENTRY:
- if (StringEqualsCaseASCII(element_name, "entry")) {
- if (strcmp(parser->song->uri, "asx:") != 0)
- parser->songs.emplace_front(parser->song);
- else
- parser->song->Free();
-
- parser->state = AsxParser::ROOT;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
- }
-}
-
-static void
-asx_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
-{
- 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 == nullptr)
- 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 SongEnumerator *
-asx_open_stream(InputStream &is)
-{
- AsxParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the ASX XML file */
-
- context = g_markup_parse_context_new(&asx_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, asx_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatErrno(asx_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatErrno(asx_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
- parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
-}
-
-static const char *const asx_suffixes[] = {
- "asx",
- nullptr
-};
-
-static const char *const asx_mime_types[] = {
- "video/x-ms-asf",
- nullptr
-};
-
-const struct playlist_plugin asx_playlist_plugin = {
- "asx",
-
- nullptr,
- nullptr,
- nullptr,
- asx_open_stream,
-
- nullptr,
- asx_suffixes,
- asx_mime_types,
-};
diff --git a/src/playlist/AsxPlaylistPlugin.hxx b/src/playlist/AsxPlaylistPlugin.hxx
deleted file mode 100644
index 240c1824a..000000000
--- a/src/playlist/AsxPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX
-#define MPD_ASX_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin asx_playlist_plugin;
-
-#endif
diff --git a/src/playlist/CloseSongEnumerator.cxx b/src/playlist/CloseSongEnumerator.cxx
new file mode 100644
index 000000000..2dddef823
--- /dev/null
+++ b/src/playlist/CloseSongEnumerator.cxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CloseSongEnumerator.hxx"
+#include "input/InputStream.hxx"
+
+CloseSongEnumerator::~CloseSongEnumerator()
+{
+ delete other;
+ delete is;
+}
+
+DetachedSong *
+CloseSongEnumerator::NextSong()
+{
+ return other->NextSong();
+}
diff --git a/src/playlist/CloseSongEnumerator.hxx b/src/playlist/CloseSongEnumerator.hxx
new file mode 100644
index 000000000..17f015394
--- /dev/null
+++ b/src/playlist/CloseSongEnumerator.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CLOSE_SONG_ENUMERATOR_HXX
+#define MPD_CLOSE_SONG_ENUMERATOR_HXX
+
+#include "SongEnumerator.hxx"
+#include "Compiler.h"
+
+class InputStream;
+
+/**
+ * A #SongEnumerator wrapper that closes an #InputStream automatically
+ * after deleting the #SongEnumerator
+ */
+class CloseSongEnumerator final : public SongEnumerator {
+ SongEnumerator *const other;
+
+ InputStream *const is;
+
+public:
+ gcc_nonnull_all
+ CloseSongEnumerator(SongEnumerator *_other, InputStream *const _is)
+ :other(_other), is(_is) {}
+
+ virtual ~CloseSongEnumerator();
+
+ virtual DetachedSong *NextSong() override;
+};
+
+#endif
diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx
deleted file mode 100644
index 42a43bbad..000000000
--- a/src/playlist/CuePlaylistPlugin.cxx
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "CuePlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "Song.hxx"
-#include "cue/CueParser.hxx"
-#include "TextInputStream.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-class CuePlaylist final : public SongEnumerator {
- InputStream &is;
- TextInputStream tis;
- CueParser parser;
-
- public:
- CuePlaylist(InputStream &_is)
- :is(_is), tis(is) {
- }
-
- virtual Song *NextSong() override;
-};
-
-static SongEnumerator *
-cue_playlist_open_stream(InputStream &is)
-{
- return new CuePlaylist(is);
-}
-
-Song *
-CuePlaylist::NextSong()
-{
- Song *song = parser.Get();
- if (song != nullptr)
- return song;
-
- std::string line;
- while (tis.ReadLine(line)) {
- parser.Feed(line.c_str());
- song = parser.Get();
- if (song != nullptr)
- return song;
- }
-
- parser.Finish();
- return parser.Get();
-}
-
-static const char *const cue_playlist_suffixes[] = {
- "cue",
- nullptr
-};
-
-static const char *const cue_playlist_mime_types[] = {
- "application/x-cue",
- nullptr
-};
-
-const struct playlist_plugin cue_playlist_plugin = {
- "cue",
-
- nullptr,
- nullptr,
- nullptr,
- cue_playlist_open_stream,
-
- nullptr,
- cue_playlist_suffixes,
- cue_playlist_mime_types,
-};
diff --git a/src/playlist/CuePlaylistPlugin.hxx b/src/playlist/CuePlaylistPlugin.hxx
deleted file mode 100644
index cf5e3a8f0..000000000
--- a/src/playlist/CuePlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index a1a865c08..000000000
--- a/src/playlist/DespotifyPlaylistPlugin.cxx
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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 "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "Song.hxx"
-#include "Log.hxx"
-
-extern "C" {
-#include <despotify.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 */
- 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 */
- FormatDebug(despotify_domain,
- "Can't add track %s", 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 SongEnumerator *
-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) {
- FormatDebug(despotify_domain, "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 MemorySongEnumerator(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,
-
- despotify_schemes,
- nullptr,
- nullptr,
-};
diff --git a/src/playlist/DespotifyPlaylistPlugin.hxx b/src/playlist/DespotifyPlaylistPlugin.hxx
deleted file mode 100644
index c1e5b7f39..000000000
--- a/src/playlist/DespotifyPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 9ad71b8a8..000000000
--- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/** \file
- *
- * 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 "SongEnumerator.hxx"
-#include "tag/Tag.hxx"
-#include "tag/TagHandler.hxx"
-#include "tag/TagId3.hxx"
-#include "tag/ApeTag.hxx"
-#include "Song.hxx"
-#include "TagFile.hxx"
-#include "cue/CueParser.hxx"
-#include "fs/Traits.hxx"
-#include "fs/AllocatedPath.hxx"
-#include "util/ASCII.hxx"
-
-#include <assert.h>
-#include <string.h>
-
-class EmbeddedCuePlaylist final : public SongEnumerator {
-public:
- /**
- * This is an override for the CUE's "FILE". An embedded CUE
- * sheet must always point to the song file it is contained
- * in.
- */
- std::string filename;
-
- /**
- * The value of the file's "CUESHEET" tag.
- */
- std::string cuesheet;
-
- /**
- * The offset of the next line within "cuesheet".
- */
- char *next;
-
- CueParser *parser;
-
-public:
- EmbeddedCuePlaylist()
- :parser(nullptr) {
- }
-
- virtual ~EmbeddedCuePlaylist() {
- delete parser;
- }
-
- virtual Song *NextSong() override;
-};
-
-static void
-embcue_tag_pair(const char *name, const char *value, void *ctx)
-{
- EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx;
-
- if (playlist->cuesheet.empty() &&
- StringEqualsCaseASCII(name, "cuesheet"))
- playlist->cuesheet = value;
-}
-
-static const struct tag_handler embcue_tag_handler = {
- nullptr,
- nullptr,
- embcue_tag_pair,
-};
-
-static SongEnumerator *
-embcue_playlist_open_uri(const char *uri,
- gcc_unused Mutex &mutex,
- gcc_unused Cond &cond)
-{
- if (!PathTraits::IsAbsoluteUTF8(uri))
- /* only local files supported */
- return nullptr;
-
- const auto path_fs = AllocatedPath::FromUTF8(uri);
- if (path_fs.IsNull())
- return nullptr;
-
- const auto playlist = new EmbeddedCuePlaylist();
-
- tag_file_scan(path_fs, &embcue_tag_handler, playlist);
- if (playlist->cuesheet.empty()) {
- tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
- if (playlist->cuesheet.empty())
- tag_id3_scan(path_fs, &embcue_tag_handler, playlist);
- }
-
- if (playlist->cuesheet.empty()) {
- /* no "CUESHEET" tag found */
- delete playlist;
- return nullptr;
- }
-
- playlist->filename = PathTraits::GetBaseUTF8(uri);
-
- playlist->next = &playlist->cuesheet[0];
- playlist->parser = new CueParser();
-
- return playlist;
-}
-
-Song *
-EmbeddedCuePlaylist::NextSong()
-{
- Song *song = parser->Get();
- if (song != nullptr)
- return song;
-
- while (*next != 0) {
- const char *line = next;
- char *eol = strpbrk(next, "\r\n");
- if (eol != nullptr) {
- /* null-terminate the line */
- *eol = 0;
- next = eol + 1;
- } else
- /* last line; put the "next" pointer to the
- end of the buffer */
- next += strlen(line);
-
- parser->Feed(line);
- song = parser->Get();
- if (song != nullptr)
- return song->ReplaceURI(filename.c_str());
- }
-
- parser->Finish();
- song = parser->Get();
- if (song != nullptr)
- song = song->ReplaceURI(filename.c_str());
- 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",
- nullptr
-};
-
-const struct playlist_plugin embcue_playlist_plugin = {
- "embcue",
-
- nullptr,
- nullptr,
- embcue_playlist_open_uri,
- nullptr,
-
- nullptr,
- embcue_playlist_suffixes,
- nullptr,
-};
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/EmbeddedCuePlaylistPlugin.hxx
deleted file mode 100644
index e306730f4..000000000
--- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index 5ef010bda..000000000
--- a/src/playlist/ExtM3uPlaylistPlugin.cxx
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "ExtM3uPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/StringUtil.hxx"
-#include "TextInputStream.hxx"
-
-#include <glib.h>
-
-#include <string.h>
-#include <stdlib.h>
-
-class ExtM3uPlaylist final : public SongEnumerator {
- TextInputStream tis;
-
-public:
- ExtM3uPlaylist(InputStream &is)
- :tis(is) {
- }
-
- bool CheckFirstLine() {
- std::string line;
- return tis.ReadLine(line) &&
- strcmp(line.c_str(), "#EXTM3U") == 0;
- }
-
- virtual Song *NextSong() override;
-};
-
-static SongEnumerator *
-extm3u_open_stream(InputStream &is)
-{
- ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is);
-
- if (!playlist->CheckFirstLine()) {
- /* no EXTM3U header: fall back to the plain m3u
- plugin */
- delete playlist;
- return NULL;
- }
-
- return 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(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;
-}
-
-Song *
-ExtM3uPlaylist::NextSong()
-{
- Tag *tag = NULL;
- std::string line;
- const char *line_s;
- Song *song;
-
- do {
- if (!tis.ReadLine(line)) {
- delete tag;
- return NULL;
- }
-
- line_s = line.c_str();
-
- if (g_str_has_prefix(line_s, "#EXTINF:")) {
- delete tag;
- tag = extm3u_parse_tag(line_s + 8);
- continue;
- }
-
- line_s = strchug_fast(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",
- "m3u8",
- nullptr
-};
-
-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,
-
- nullptr,
- extm3u_suffixes,
- extm3u_mime_types,
-};
diff --git a/src/playlist/ExtM3uPlaylistPlugin.hxx b/src/playlist/ExtM3uPlaylistPlugin.hxx
deleted file mode 100644
index 844fba15c..000000000
--- a/src/playlist/ExtM3uPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
-#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin extm3u_playlist_plugin;
-
-#endif
diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx
deleted file mode 100644
index 8b6adc2b6..000000000
--- a/src/playlist/M3uPlaylistPlugin.cxx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "M3uPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "SongEnumerator.hxx"
-#include "Song.hxx"
-#include "util/StringUtil.hxx"
-#include "TextInputStream.hxx"
-
-class M3uPlaylist final : public SongEnumerator {
- TextInputStream tis;
-
-public:
- M3uPlaylist(InputStream &is)
- :tis(is) {
- }
-
- virtual Song *NextSong() override;
-};
-
-static SongEnumerator *
-m3u_open_stream(InputStream &is)
-{
- return new M3uPlaylist(is);
-}
-
-Song *
-M3uPlaylist::NextSong()
-{
- std::string line;
- const char *line_s;
-
- do {
- if (!tis.ReadLine(line))
- return nullptr;
-
- line_s = line.c_str();
- line_s = strchug_fast(line_s);
- } while (line_s[0] == '#' || *line_s == 0);
-
- return Song::NewRemote(line_s);
-}
-
-static const char *const m3u_suffixes[] = {
- "m3u",
- "m3u8",
- nullptr
-};
-
-static const char *const m3u_mime_types[] = {
- "audio/x-mpegurl",
- nullptr
-};
-
-const struct playlist_plugin m3u_playlist_plugin = {
- "m3u",
-
- nullptr,
- nullptr,
- nullptr,
- m3u_open_stream,
-
- nullptr,
- m3u_suffixes,
- m3u_mime_types,
-};
diff --git a/src/playlist/M3uPlaylistPlugin.hxx b/src/playlist/M3uPlaylistPlugin.hxx
deleted file mode 100644
index a2058bb29..000000000
--- a/src/playlist/M3uPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX
-#define MPD_M3U_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin m3u_playlist_plugin;
-
-#endif
diff --git a/src/playlist/MemorySongEnumerator.cxx b/src/playlist/MemorySongEnumerator.cxx
new file mode 100644
index 000000000..c3127c2bf
--- /dev/null
+++ b/src/playlist/MemorySongEnumerator.cxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MemorySongEnumerator.hxx"
+
+DetachedSong *
+MemorySongEnumerator::NextSong()
+{
+ if (songs.empty())
+ return nullptr;
+
+ auto result = new DetachedSong(std::move(songs.front()));
+ songs.pop_front();
+ return result;
+}
diff --git a/src/playlist/MemorySongEnumerator.hxx b/src/playlist/MemorySongEnumerator.hxx
new file mode 100644
index 000000000..d1259f011
--- /dev/null
+++ b/src/playlist/MemorySongEnumerator.hxx
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_MEMORY_PLAYLIST_PROVIDER_HXX
+#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
+
+#include "SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "Compiler.h"
+
+#include <forward_list>
+
+class MemorySongEnumerator final : public SongEnumerator {
+ std::forward_list<DetachedSong> songs;
+
+public:
+ MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs)
+ :songs(std::move(_songs)) {}
+
+ virtual DetachedSong *NextSong() override;
+};
+
+#endif
diff --git a/src/playlist/PlaylistAny.cxx b/src/playlist/PlaylistAny.cxx
new file mode 100644
index 000000000..7093fb99a
--- /dev/null
+++ b/src/playlist/PlaylistAny.cxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistAny.hxx"
+#include "PlaylistStream.hxx"
+#include "PlaylistMapper.hxx"
+#include "util/UriUtil.hxx"
+
+SongEnumerator *
+playlist_open_any(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond)
+{
+ return uri_has_scheme(uri)
+ ? playlist_open_remote(uri, mutex, cond)
+ : playlist_mapper_open(uri,
+#ifdef ENABLE_DATABASE
+ storage,
+#endif
+ mutex, cond);
+}
diff --git a/src/playlist/PlaylistAny.hxx b/src/playlist/PlaylistAny.hxx
new file mode 100644
index 000000000..23b0075b6
--- /dev/null
+++ b/src/playlist/PlaylistAny.hxx
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_ANY_HXX
+#define MPD_PLAYLIST_ANY_HXX
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class Storage;
+
+/**
+ * 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.
+ */
+SongEnumerator *
+playlist_open_any(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond);
+
+#endif
diff --git a/src/playlist/PlaylistMapper.cxx b/src/playlist/PlaylistMapper.cxx
new file mode 100644
index 000000000..042a39d34
--- /dev/null
+++ b/src/playlist/PlaylistMapper.cxx
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistMapper.hxx"
+#include "PlaylistFile.hxx"
+#include "PlaylistStream.hxx"
+#include "PlaylistRegistry.hxx"
+#include "Mapper.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "storage/StorageInterface.hxx"
+#include "util/UriUtil.hxx"
+
+#include <assert.h>
+
+/**
+ * Load a playlist from the configured playlist directory.
+ */
+static SongEnumerator *
+playlist_open_in_playlist_dir(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(spl_valid_name(uri));
+
+ const auto path_fs = map_spl_utf8_to_fs(uri);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ return playlist_open_path(path_fs, mutex, cond);
+}
+
+#ifdef ENABLE_DATABASE
+
+/**
+ * Load a playlist from the configured music directory.
+ */
+static SongEnumerator *
+playlist_open_in_storage(const char *uri, const Storage *storage,
+ Mutex &mutex, Cond &cond)
+{
+ assert(uri_safe_local(uri));
+
+ if (storage == nullptr)
+ return nullptr;
+
+ {
+ const auto path = storage->MapFS(uri);
+ if (!path.IsNull())
+ return playlist_open_path(path, mutex, cond);
+ }
+
+ const auto uri2 = storage->MapUTF8(uri);
+ return playlist_open_remote(uri2.c_str(), mutex, cond);
+}
+
+#endif
+
+SongEnumerator *
+playlist_mapper_open(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond)
+{
+ if (spl_valid_name(uri)) {
+ auto playlist = playlist_open_in_playlist_dir(uri,
+ mutex, cond);
+ if (playlist != nullptr)
+ return playlist;
+ }
+
+#ifdef ENABLE_DATABASE
+ if (uri_safe_local(uri)) {
+ auto playlist = playlist_open_in_storage(uri, storage,
+ mutex, cond);
+ if (playlist != nullptr)
+ return playlist;
+ }
+#endif
+
+ return nullptr;
+}
diff --git a/src/playlist/PlaylistMapper.hxx b/src/playlist/PlaylistMapper.hxx
new file mode 100644
index 000000000..29ce45083
--- /dev/null
+++ b/src/playlist/PlaylistMapper.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_MAPPER_HXX
+#define MPD_PLAYLIST_MAPPER_HXX
+
+#include "check.h"
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class Storage;
+
+/**
+ * Opens a playlist from an URI relative to the playlist or music
+ * directory.
+ */
+SongEnumerator *
+playlist_mapper_open(const char *uri,
+#ifdef ENABLE_DATABASE
+ const Storage *storage,
+#endif
+ Mutex &mutex, Cond &cond);
+
+#endif
diff --git a/src/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx
new file mode 100644
index 000000000..fd779ad8d
--- /dev/null
+++ b/src/playlist/PlaylistPlugin.hxx
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_PLUGIN_HXX
+#define MPD_PLAYLIST_PLUGIN_HXX
+
+struct config_param;
+class InputStream;
+struct Tag;
+class Mutex;
+class Cond;
+class SongEnumerator;
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the 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 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.
+ */
+ SongEnumerator *(*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.
+ */
+ SongEnumerator *(*open_stream)(InputStream &is);
+
+ 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 nullptr 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 != nullptr
+ ? 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 != nullptr)
+ plugin->finish();
+}
+
+static inline SongEnumerator *
+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 SongEnumerator *
+playlist_plugin_open_stream(const struct playlist_plugin *plugin,
+ InputStream &is)
+{
+ return plugin->open_stream(is);
+}
+
+#endif
diff --git a/src/playlist/PlaylistQueue.cxx b/src/playlist/PlaylistQueue.cxx
new file mode 100644
index 000000000..b10a26172
--- /dev/null
+++ b/src/playlist/PlaylistQueue.cxx
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistQueue.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "queue/Playlist.hxx"
+#include "SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "fs/Traits.hxx"
+#include "util/Error.hxx"
+
+#ifdef ENABLE_DATABASE
+#include "SongLoader.hxx"
+#endif
+
+bool
+playlist_load_into_queue(const char *uri, SongEnumerator &e,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader,
+ Error &error)
+{
+ const std::string base_uri = uri != nullptr
+ ? PathTraitsUTF8::GetParent(uri)
+ : std::string(".");
+
+ DetachedSong *song;
+ for (unsigned i = 0;
+ i < end_index && (song = e.NextSong()) != nullptr;
+ ++i) {
+ if (i < start_index) {
+ /* skip songs before the start index */
+ delete song;
+ continue;
+ }
+
+ if (!playlist_check_translate_song(*song, base_uri.c_str(),
+ loader)) {
+ delete song;
+ continue;
+ }
+
+ unsigned id = dest.AppendSong(pc, std::move(*song), error);
+ delete song;
+ if (id == 0)
+ return false;
+ }
+
+ return true;
+}
+
+bool
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader,
+ Error &error)
+{
+ Mutex mutex;
+ Cond cond;
+
+ auto playlist = playlist_open_any(uri,
+#ifdef ENABLE_DATABASE
+ loader.GetStorage(),
+#endif
+ mutex, cond);
+ if (playlist == nullptr) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_LIST),
+ "No such playlist");
+ return false;
+ }
+
+ bool result =
+ playlist_load_into_queue(uri, *playlist,
+ start_index, end_index,
+ dest, pc, loader, error);
+ delete playlist;
+ return result;
+}
diff --git a/src/playlist/PlaylistQueue.hxx b/src/playlist/PlaylistQueue.hxx
new file mode 100644
index 000000000..28eb86fcc
--- /dev/null
+++ b/src/playlist/PlaylistQueue.hxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*! \file
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_HXX
+#define MPD_PLAYLIST_QUEUE_HXX
+
+#include "PlaylistError.hxx"
+
+class Error;
+class SongLoader;
+class SongEnumerator;
+struct playlist;
+struct PlayerControl;
+
+/**
+ * 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)
+ */
+bool
+playlist_load_into_queue(const char *uri, SongEnumerator &e,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader,
+ Error &error);
+
+/**
+ * Opens a playlist with a playlist plugin and append to the specified
+ * play queue.
+ */
+bool
+playlist_open_into_queue(const char *uri,
+ unsigned start_index, unsigned end_index,
+ playlist &dest, PlayerControl &pc,
+ const SongLoader &loader, Error &error);
+
+#endif
+
diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx
new file mode 100644
index 000000000..4e9ef890e
--- /dev/null
+++ b/src/playlist/PlaylistRegistry.cxx
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistRegistry.hxx"
+#include "PlaylistPlugin.hxx"
+#include "plugins/ExtM3uPlaylistPlugin.hxx"
+#include "plugins/M3uPlaylistPlugin.hxx"
+#include "plugins/XspfPlaylistPlugin.hxx"
+#include "plugins/DespotifyPlaylistPlugin.hxx"
+#include "plugins/SoundCloudPlaylistPlugin.hxx"
+#include "plugins/PlsPlaylistPlugin.hxx"
+#include "plugins/AsxPlaylistPlugin.hxx"
+#include "plugins/RssPlaylistPlugin.hxx"
+#include "plugins/CuePlaylistPlugin.hxx"
+#include "plugins/EmbeddedCuePlaylistPlugin.hxx"
+#include "input/InputStream.hxx"
+#include "util/UriUtil.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Macros.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+#ifdef HAVE_GLIB
+ // TODO: enable without GLib
+ &pls_playlist_plugin,
+#endif
+#ifdef HAVE_EXPAT
+ &xspf_playlist_plugin,
+ &asx_playlist_plugin,
+ &rss_playlist_plugin,
+#endif
+#ifdef ENABLE_DESPOTIFY
+ &despotify_playlist_plugin,
+#endif
+#ifdef ENABLE_SOUNDCLOUD
+ &soundcloud_playlist_plugin,
+#endif
+ &cue_playlist_plugin,
+ &embcue_playlist_plugin,
+ nullptr
+};
+
+static constexpr unsigned n_playlist_plugins =
+ ARRAY_SIZE(playlist_plugins) - 1;
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[n_playlist_plugins];
+
+#define playlist_plugins_for_each_enabled(plugin) \
+ playlist_plugins_for_each(plugin) \
+ if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins])
+
+void
+playlist_list_global_init(void)
+{
+ const config_param empty;
+
+ for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ config_find_block(CONF_PLAYLIST_PLUGIN, "name",
+ 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 SongEnumerator *
+playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond,
+ bool *tried)
+{
+ SongEnumerator *playlist = nullptr;
+
+ assert(uri != nullptr);
+
+ const auto scheme = uri_get_scheme(uri);
+ if (scheme.empty())
+ return nullptr;
+
+ for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ assert(!tried[i]);
+
+ if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr &&
+ plugin->schemes != nullptr &&
+ string_array_contains(plugin->schemes, scheme.c_str())) {
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
+ if (playlist != nullptr)
+ break;
+
+ tried[i] = true;
+ }
+ }
+
+ return playlist;
+}
+
+static SongEnumerator *
+playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
+ const bool *tried)
+{
+ SongEnumerator *playlist = nullptr;
+
+ assert(uri != nullptr);
+
+ UriSuffixBuffer suffix_buffer;
+ const char *const suffix = uri_get_suffix(uri, suffix_buffer);
+ if (suffix == nullptr)
+ return nullptr;
+
+ for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && !tried[i] &&
+ plugin->open_uri != nullptr && plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ playlist = playlist_plugin_open_uri(plugin, uri,
+ mutex, cond);
+ if (playlist != nullptr)
+ break;
+ }
+ }
+
+ return playlist;
+}
+
+SongEnumerator *
+playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ /** this array tracks which plugins have already been tried by
+ playlist_list_open_uri_scheme() */
+ bool tried[n_playlist_plugins];
+
+ assert(uri != nullptr);
+
+ memset(tried, false, sizeof(tried));
+
+ auto playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried);
+ if (playlist == nullptr)
+ playlist = playlist_list_open_uri_suffix(uri, mutex, cond,
+ tried);
+
+ return playlist;
+}
+
+static SongEnumerator *
+playlist_list_open_stream_mime2(InputStream &is, const char *mime)
+{
+ assert(mime != nullptr);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->open_stream != nullptr &&
+ plugin->mime_types != nullptr &&
+ string_array_contains(plugin->mime_types, mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ is.Rewind(IgnoreError());
+
+ auto playlist = playlist_plugin_open_stream(plugin,
+ is);
+ if (playlist != nullptr)
+ return playlist;
+ }
+ }
+
+ return nullptr;
+}
+
+static SongEnumerator *
+playlist_list_open_stream_mime(InputStream &is, const char *full_mime)
+{
+ assert(full_mime != nullptr);
+
+ const char *semicolon = strchr(full_mime, ';');
+ if (semicolon == nullptr)
+ return playlist_list_open_stream_mime2(is, full_mime);
+
+ if (semicolon == full_mime)
+ return nullptr;
+
+ /* probe only the portion before the semicolon*/
+ const std::string mime(full_mime, semicolon);
+ return playlist_list_open_stream_mime2(is, mime.c_str());
+}
+
+SongEnumerator *
+playlist_list_open_stream_suffix(InputStream &is, const char *suffix)
+{
+ assert(suffix != nullptr);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->open_stream != nullptr &&
+ plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ is.Rewind(IgnoreError());
+
+ auto playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != nullptr)
+ return playlist;
+ }
+ }
+
+ return nullptr;
+}
+
+SongEnumerator *
+playlist_list_open_stream(InputStream &is, const char *uri)
+{
+ assert(is.IsReady());
+
+ const char *const mime = is.GetMimeType();
+ if (mime != nullptr) {
+ auto playlist = playlist_list_open_stream_mime(is, mime);
+ if (playlist != nullptr)
+ return playlist;
+ }
+
+ UriSuffixBuffer suffix_buffer;
+ const char *suffix = uri != nullptr
+ ? uri_get_suffix(uri, suffix_buffer)
+ : nullptr;
+ if (suffix != nullptr) {
+ auto playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != nullptr)
+ return playlist;
+ }
+
+ return nullptr;
+}
+
+bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != nullptr);
+
+ playlist_plugins_for_each_enabled(plugin) {
+ if (plugin->suffixes != nullptr &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/playlist/PlaylistRegistry.hxx b/src/playlist/PlaylistRegistry.hxx
new file mode 100644
index 000000000..7ce559baa
--- /dev/null
+++ b/src/playlist/PlaylistRegistry.hxx
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_REGISTRY_HXX
+#define MPD_PLAYLIST_REGISTRY_HXX
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class InputStream;
+
+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) != nullptr; \
+ ++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.
+ */
+SongEnumerator *
+playlist_list_open_uri(const char *uri, Mutex &mutex, Cond &cond);
+
+SongEnumerator *
+playlist_list_open_stream_suffix(InputStream &is, const char *suffix);
+
+/**
+ * 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
+ */
+SongEnumerator *
+playlist_list_open_stream(InputStream &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);
+
+#endif
diff --git a/src/playlist/PlaylistSong.cxx b/src/playlist/PlaylistSong.cxx
new file mode 100644
index 000000000..3603c1add
--- /dev/null
+++ b/src/playlist/PlaylistSong.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistSong.hxx"
+#include "SongLoader.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "fs/Traits.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "DetachedSong.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+static void
+merge_song_metadata(DetachedSong &add, const DetachedSong &base)
+{
+ if (base.GetTag().IsDefined()) {
+ TagBuilder builder(add.GetTag());
+ builder.Complement(base.GetTag());
+ add.SetTag(builder.Commit());
+ }
+
+ add.SetLastModified(base.GetLastModified());
+}
+
+static bool
+playlist_check_load_song(DetachedSong &song, const SongLoader &loader)
+{
+ DetachedSong *tmp = loader.LoadSong(song.GetURI(), IgnoreError());
+ if (tmp == nullptr)
+ return false;
+
+ song.SetURI(tmp->GetURI());
+ if (!song.HasRealURI() && tmp->HasRealURI())
+ song.SetRealURI(tmp->GetRealURI());
+
+ merge_song_metadata(song, *tmp);
+ delete tmp;
+ return true;
+}
+
+bool
+playlist_check_translate_song(DetachedSong &song, const char *base_uri,
+ const SongLoader &loader)
+{
+ if (base_uri != nullptr && strcmp(base_uri, ".") == 0)
+ /* PathTraitsUTF8::GetParent() returns "." when there
+ is no directory name in the given path; clear that
+ now, because it would break the database lookup
+ functions */
+ base_uri = nullptr;
+
+ const char *uri = song.GetURI();
+ if (base_uri != nullptr && !uri_has_scheme(uri) &&
+ !PathTraitsUTF8::IsAbsolute(uri))
+ song.SetURI(PathTraitsUTF8::Build(base_uri, uri));
+
+ return playlist_check_load_song(song, loader);
+}
diff --git a/src/playlist/PlaylistSong.hxx b/src/playlist/PlaylistSong.hxx
new file mode 100644
index 000000000..278df46a8
--- /dev/null
+++ b/src/playlist/PlaylistSong.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_SONG_HXX
+#define MPD_PLAYLIST_SONG_HXX
+
+class SongLoader;
+class DetachedSong;
+
+/**
+ * Verifies the song, returns false if it is unsafe. Translate the
+ * song to a song within the database, if it is a local file.
+ *
+ * @return true on success, false if the song should not be used
+ */
+bool
+playlist_check_translate_song(DetachedSong &song, const char *base_uri,
+ const SongLoader &loader);
+
+#endif
diff --git a/src/playlist/PlaylistStream.cxx b/src/playlist/PlaylistStream.cxx
new file mode 100644
index 000000000..074f39d66
--- /dev/null
+++ b/src/playlist/PlaylistStream.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlaylistStream.hxx"
+#include "PlaylistRegistry.hxx"
+#include "CloseSongEnumerator.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "input/InputStream.hxx"
+#include "input/LocalOpen.hxx"
+#include "fs/Path.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+static SongEnumerator *
+playlist_open_path_suffix(Path path, Mutex &mutex, Cond &cond)
+{
+ assert(!path.IsNull());
+
+ const char *suffix = uri_get_suffix(path.c_str());
+ if (suffix == nullptr || !playlist_suffix_supported(suffix))
+ return nullptr;
+
+ Error error;
+ InputStream *is = OpenLocalInputStream(path, mutex, cond, error);
+ if (is == nullptr) {
+ LogError(error);
+ return nullptr;
+ }
+
+ auto playlist = playlist_list_open_stream_suffix(*is, suffix);
+ if (playlist != nullptr)
+ playlist = new CloseSongEnumerator(playlist, is);
+ else
+ delete is;
+
+ return playlist;
+}
+
+SongEnumerator *
+playlist_open_path(Path path, Mutex &mutex, Cond &cond)
+{
+ assert(!path.IsNull());
+
+ const std::string uri_utf8 = path.ToUTF8();
+ auto playlist = !uri_utf8.empty()
+ ? playlist_list_open_uri(uri_utf8.c_str(), mutex, cond)
+ : nullptr;
+ if (playlist == nullptr)
+ playlist = playlist_open_path_suffix(path, mutex, cond);
+
+ return playlist;
+}
+
+SongEnumerator *
+playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(uri_has_scheme(uri));
+
+ SongEnumerator *playlist = playlist_list_open_uri(uri, mutex, cond);
+ if (playlist != nullptr)
+ return playlist;
+
+ Error error;
+ InputStream *is = InputStream::OpenReady(uri, mutex, cond, error);
+ if (is == nullptr) {
+ if (error.IsDefined())
+ FormatError(error, "Failed to open %s", uri);
+
+ return nullptr;
+ }
+
+ playlist = playlist_list_open_stream(*is, uri);
+ if (playlist == nullptr) {
+ delete is;
+ return nullptr;
+ }
+
+ return new CloseSongEnumerator(playlist, is);
+}
diff --git a/src/playlist/PlaylistStream.hxx b/src/playlist/PlaylistStream.hxx
new file mode 100644
index 000000000..c07ae0b09
--- /dev/null
+++ b/src/playlist/PlaylistStream.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_STREAM_HXX
+#define MPD_PLAYLIST_STREAM_HXX
+
+#include "Compiler.h"
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+class Path;
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param path 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 nullptr on error
+ */
+gcc_nonnull_all
+SongEnumerator *
+playlist_open_path(Path path, Mutex &mutex, Cond &cond);
+
+gcc_nonnull_all
+SongEnumerator *
+playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond);
+
+#endif
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx
deleted file mode 100644
index 7b5c8824c..000000000
--- a/src/playlist/PlsPlaylistPlugin.cxx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "PlsPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <string>
-#include <stdio.h>
-
-static constexpr Domain pls_domain("pls");
-
-static void
-pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
-{
- gchar *value;
- int length;
- GError *error = nullptr;
- int num_entries = g_key_file_get_integer(keyfile, "playlist",
- "NumberOfEntries", &error);
- if (error) {
- FormatError(pls_domain,
- "Invalid PLS file: '%s'", error->message);
- g_error_free(error);
- error = nullptr;
-
- /* Hack to work around shoutcast failure to comform to spec */
- num_entries = g_key_file_get_integer(keyfile, "playlist",
- "numberofentries", &error);
- if (error) {
- g_error_free(error);
- error = nullptr;
- }
- }
-
- while (num_entries > 0) {
- Song *song;
-
- char key[64];
- sprintf(key, "File%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error) {
- FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
- key, error->message);
- g_error_free(error);
- return;
- }
-
- song = Song::NewRemote(value);
- g_free(value);
-
- sprintf(key, "Title%u", num_entries);
- value = g_key_file_get_string(keyfile, "playlist", key,
- &error);
- if(error == nullptr && value){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->AddItem(TAG_TITLE, value);
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
- g_free(value);
-
- sprintf(key, "Length%u", num_entries);
- length = g_key_file_get_integer(keyfile, "playlist", key,
- &error);
- if(error == nullptr && length > 0){
- if (song->tag == nullptr)
- song->tag = new Tag();
- song->tag->time = length;
- }
- /* Ignore errors? Most likely value not present */
- if(error) g_error_free(error);
- error = nullptr;
-
- songs.emplace_front(song);
- num_entries--;
- }
-
-}
-
-static SongEnumerator *
-pls_open_stream(InputStream &is)
-{
- GError *error = nullptr;
- Error error2;
- size_t nbytes;
- char buffer[1024];
- bool success;
- GKeyFile *keyfile;
-
- std::string kf_data;
-
- do {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- kf_data.append(buffer, nbytes);
- /* Limit to 64k */
- } while (kf_data.length() < 65536);
-
- if (kf_data.empty()) {
- LogWarning(pls_domain, "KeyFile parser failed: No Data");
- return nullptr;
- }
-
- keyfile = g_key_file_new();
- success = g_key_file_load_from_data(keyfile,
- kf_data.data(), kf_data.length(),
- G_KEY_FILE_NONE, &error);
-
- if (!success) {
- FormatError(pls_domain,
- "KeyFile parser failed: %s", error->message);
- g_error_free(error);
- g_key_file_free(keyfile);
- return nullptr;
- }
-
- std::forward_list<SongPointer> songs;
- pls_parser(keyfile, songs);
- g_key_file_free(keyfile);
-
- return new MemorySongEnumerator(std::move(songs));
-}
-
-static const char *const pls_suffixes[] = {
- "pls",
- nullptr
-};
-
-static const char *const pls_mime_types[] = {
- "audio/x-scpls",
- nullptr
-};
-
-const struct playlist_plugin pls_playlist_plugin = {
- "pls",
-
- nullptr,
- nullptr,
- nullptr,
- pls_open_stream,
-
- nullptr,
- pls_suffixes,
- pls_mime_types,
-};
diff --git a/src/playlist/PlsPlaylistPlugin.hxx b/src/playlist/PlsPlaylistPlugin.hxx
deleted file mode 100644
index 3fafd36d0..000000000
--- a/src/playlist/PlsPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX
-#define MPD_PLS_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin pls_playlist_plugin;
-
-#endif
diff --git a/src/playlist/Print.cxx b/src/playlist/Print.cxx
new file mode 100644
index 000000000..8f743f56d
--- /dev/null
+++ b/src/playlist/Print.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Print.hxx"
+#include "PlaylistAny.hxx"
+#include "PlaylistSong.hxx"
+#include "SongEnumerator.hxx"
+#include "SongPrint.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
+#include "fs/Traits.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "client/Client.hxx"
+
+static void
+playlist_provider_print(Client &client, const char *uri,
+ SongEnumerator &e, bool detail)
+{
+ const std::string base_uri = uri != nullptr
+ ? PathTraitsUTF8::GetParent(uri)
+ : std::string(".");
+
+ const SongLoader loader(client);
+
+ DetachedSong *song;
+ while ((song = e.NextSong()) != nullptr) {
+ if (playlist_check_translate_song(*song, base_uri.c_str(),
+ loader) &&
+ detail)
+ song_print_info(client, *song);
+ else
+ /* fallback if no detail was requested or no
+ detail was available */
+ song_print_uri(client, *song);
+
+ delete song;
+ }
+}
+
+bool
+playlist_file_print(Client &client, const char *uri, bool detail)
+{
+ Mutex mutex;
+ Cond cond;
+
+ SongEnumerator *playlist = playlist_open_any(uri,
+#ifdef ENABLE_DATABASE
+ client.GetStorage(),
+#endif
+ mutex, cond);
+ if (playlist == nullptr)
+ return false;
+
+ playlist_provider_print(client, uri, *playlist, detail);
+ delete playlist;
+ return true;
+}
diff --git a/src/playlist/Print.hxx b/src/playlist/Print.hxx
new file mode 100644
index 000000000..c2fff5475
--- /dev/null
+++ b/src/playlist/Print.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST__PRINT_HXX
+#define MPD_PLAYLIST__PRINT_HXX
+
+class Client;
+
+/**
+ * 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/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
deleted file mode 100644
index e2a44bfd3..000000000
--- a/src/playlist/RssPlaylistPlugin.cxx
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "RssPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/ASCII.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain rss_domain("rss");
-
-/**
- * This is the state object for the GLib XML parser.
- */
-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.
- */
- TagType 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] != nullptr; ++i)
- if (StringEqualsCaseASCII(attribute_names[i], name))
- return attribute_values[i];
-
- return nullptr;
-}
-
-static void
-rss_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- const gchar **attribute_names,
- const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- if (StringEqualsCaseASCII(element_name, "item")) {
- parser->state = RssParser::ITEM;
- parser->song = Song::NewRemote("rss:");
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
- }
-
- break;
-
- case RssParser::ITEM:
- if (StringEqualsCaseASCII(element_name, "enclosure")) {
- const gchar *href = get_attribute(attribute_names,
- attribute_values,
- "url");
- if (href != nullptr) {
- /* create new song object, and copy
- the existing tag over; we cannot
- replace the existing song's URI,
- because that attribute is
- immutable */
- Song *song = Song::NewRemote(href);
-
- if (parser->song != nullptr) {
- song->tag = parser->song->tag;
- parser->song->tag = nullptr;
- parser->song->Free();
- }
-
- parser->song = song;
- }
- } else if (StringEqualsCaseASCII(element_name, "title"))
- parser->tag = TAG_TITLE;
- else if (StringEqualsCaseASCII(element_name, "itunes:author"))
- parser->tag = TAG_ARTIST;
-
- break;
- }
-}
-
-static void
-rss_end_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_unused GError **error)
-{
- RssParser *parser = (RssParser *)user_data;
-
- switch (parser->state) {
- case RssParser::ROOT:
- break;
-
- case RssParser::ITEM:
- if (StringEqualsCaseASCII(element_name, "item")) {
- if (strcmp(parser->song->uri, "rss:") != 0)
- parser->songs.emplace_front(parser->song);
- else
- parser->song->Free();
-
- parser->state = RssParser::ROOT;
- } else
- parser->tag = TAG_NUM_OF_ITEM_TYPES;
-
- break;
- }
-}
-
-static void
-rss_text(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_unused GError **error)
-{
- 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 == nullptr)
- 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 SongEnumerator *
-rss_open_stream(InputStream &is)
-{
- RssParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the RSS XML file */
-
- context = g_markup_parse_context_new(&rss_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, rss_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(rss_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
- parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
-}
-
-static const char *const rss_suffixes[] = {
- "rss",
- nullptr
-};
-
-static const char *const rss_mime_types[] = {
- "application/rss+xml",
- "text/xml",
- nullptr
-};
-
-const struct playlist_plugin rss_playlist_plugin = {
- "rss",
-
- nullptr,
- nullptr,
- nullptr,
- rss_open_stream,
-
- nullptr,
- rss_suffixes,
- rss_mime_types,
-};
diff --git a/src/playlist/RssPlaylistPlugin.hxx b/src/playlist/RssPlaylistPlugin.hxx
deleted file mode 100644
index f49f7e9cf..000000000
--- a/src/playlist/RssPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX
-#define MPD_RSS_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin rss_playlist_plugin;
-
-#endif
diff --git a/src/playlist/SongEnumerator.hxx b/src/playlist/SongEnumerator.hxx
new file mode 100644
index 000000000..75295add1
--- /dev/null
+++ b/src/playlist/SongEnumerator.hxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SONG_ENUMERATOR_HXX
+#define MPD_SONG_ENUMERATOR_HXX
+
+class DetachedSong;
+
+/**
+ * An object which provides serial access to a number of #Song
+ * objects. It is used to enumerate the contents of a playlist file.
+ */
+class SongEnumerator {
+public:
+ virtual ~SongEnumerator() {}
+
+ /**
+ * Obtain the next song. The caller is responsible for
+ * freeing the returned #Song object. Returns nullptr if
+ * there are no more songs.
+ */
+ virtual DetachedSong *NextSong() = 0;
+};
+
+#endif
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx
deleted file mode 100644
index f6797b14d..000000000
--- a/src/playlist/SoundCloudPlaylistPlugin.cxx
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "SoundCloudPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "ConfigData.hxx"
-#include "InputStream.hxx"
-#include "Song.hxx"
-#include "tag/Tag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-#include <yajl/yajl_parse.h>
-
-#include <string>
-
-#include <string.h>
-
-static struct {
- std::string apikey;
-} soundcloud_config;
-
-static constexpr Domain soundcloud_domain("soundcloud");
-
-static bool
-soundcloud_init(const config_param &param)
-{
- soundcloud_config.apikey = param.GetBlockValue("apikey", "");
- if (soundcloud_config.apikey.empty()) {
- LogDebug(soundcloud_domain,
- "disabling the soundcloud playlist plugin "
- "because API key is not set");
- return false;
- }
-
- return true;
-}
-
-/**
- * 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, nullptr);
- } else {
- /* assume it's just a path on soundcloud.com */
- u = g_strconcat("http://soundcloud.com/", uri, nullptr);
- }
-
- ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=",
- u, "&client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- 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",
- nullptr,
-};
-
-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 != nullptr)
- g_free(data->title);
- data->title = g_strndup(s, stringlen);
- break;
- case Stream_URL:
- if (data->stream_url != nullptr)
- 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 (memcmp((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.c_str(), nullptr);
- s = Song::NewRemote(u);
- g_free(u);
-
- Tag *t = new Tag();
- t->time = data->duration / 1000;
- if (data->title != nullptr)
- t->AddItem(TAG_NAME, data->title);
- s->tag = t;
-
- data->songs.emplace_front(s);
-
- return 1;
-}
-
-static yajl_callbacks parse_callbacks = {
- nullptr,
- nullptr,
- handle_integer,
- nullptr,
- nullptr,
- handle_string,
- handle_start_map,
- handle_mapkey,
- handle_end_map,
- nullptr,
- nullptr,
-};
-
-/**
- * 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)
-{
- char buffer[4096];
- unsigned char *ubuffer = (unsigned char *)buffer;
-
- Error error;
- InputStream *input_stream = InputStream::Open(url, mutex, cond,
- error);
- if (input_stream == nullptr) {
- if (error.IsDefined())
- LogError(error);
- return -1;
- }
-
- mutex.lock();
- input_stream->WaitReady();
-
- yajl_status stat;
- int done = 0;
-
- while (!done) {
- const size_t nbytes =
- input_stream->Read(buffer, sizeof(buffer), error);
- if (nbytes == 0) {
- if (error.IsDefined())
- LogError(error);
-
- if (input_stream->IsEOF()) {
- done = true;
- } else {
- mutex.unlock();
- input_stream->Close();
- 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);
- LogError(soundcloud_domain, (const char *)str);
- yajl_free_error(hand, str);
- break;
- }
- }
-
- mutex.unlock();
- input_stream->Close();
-
- 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 SongEnumerator *
-soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
-{
- char *s, *p;
- char *scheme, *arg, *rest;
- s = g_strdup(uri);
- scheme = s;
- for (p = s; *p; p++) {
- if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') {
- *p = 0;
- p += 3;
- break;
- }
- }
- arg = p;
- for (; *p; p++) {
- if (*p == '/') {
- *p = 0;
- p++;
- break;
- }
- }
- rest = p;
-
- if (strcmp(scheme, "soundcloud") != 0) {
- FormatWarning(soundcloud_domain,
- "incompatible scheme for soundcloud plugin: %s",
- scheme);
- g_free(s);
- return nullptr;
- }
-
- char *u = nullptr;
- if (strcmp(arg, "track") == 0) {
- u = g_strconcat("http://api.soundcloud.com/tracks/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- } else if (strcmp(arg, "playlist") == 0) {
- u = g_strconcat("http://api.soundcloud.com/playlists/",
- rest, ".json?client_id=",
- soundcloud_config.apikey.c_str(), nullptr);
- } 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 == nullptr) {
- LogWarning(soundcloud_domain, "unknown soundcloud URI");
- return nullptr;
- }
-
- yajl_handle hand;
- struct parse_data data;
-
- data.got_url = 0;
- data.title = nullptr;
- data.stream_url = nullptr;
-#ifdef HAVE_YAJL1
- hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, (void *) &data);
-#else
- hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data);
-#endif
-
- int ret = soundcloud_parse_json(u, hand, mutex, cond);
-
- g_free(u);
- yajl_free(hand);
- if (data.title != nullptr)
- g_free(data.title);
- if (data.stream_url != nullptr)
- g_free(data.stream_url);
-
- if (ret == -1)
- return nullptr;
-
- data.songs.reverse();
- return new MemorySongEnumerator(std::move(data.songs));
-}
-
-static const char *const soundcloud_schemes[] = {
- "soundcloud",
- nullptr
-};
-
-const struct playlist_plugin soundcloud_playlist_plugin = {
- "soundcloud",
-
- soundcloud_init,
- nullptr,
- soundcloud_open_uri,
- nullptr,
-
- soundcloud_schemes,
- nullptr,
- nullptr,
-};
-
-
diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx
deleted file mode 100644
index 7c121328c..000000000
--- a/src/playlist/SoundCloudPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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
deleted file mode 100644
index dcfab5a80..000000000
--- a/src/playlist/XspfPlaylistPlugin.cxx
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "config.h"
-#include "XspfPlaylistPlugin.hxx"
-#include "PlaylistPlugin.hxx"
-#include "MemorySongEnumerator.hxx"
-#include "InputStream.hxx"
-#include "tag/Tag.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
-#include "Log.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static constexpr Domain xspf_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.
- */
- TagType tag;
-
- /**
- * The current song. It is allocated after the "location"
- * element.
- */
- Song *song;
-
- XspfParser()
- :state(ROOT) {}
-};
-
-static void
-xspf_start_element(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gcc_unused const gchar **attribute_names,
- gcc_unused const gchar **attribute_values,
- gpointer user_data, gcc_unused GError **error)
-{
- 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 = nullptr;
- 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(gcc_unused GMarkupParseContext *context,
- const gchar *element_name,
- gpointer user_data, gcc_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 != nullptr)
- 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(gcc_unused GMarkupParseContext *context,
- const gchar *text, gsize text_len,
- gpointer user_data, gcc_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 != nullptr &&
- parser->tag != TAG_NUM_OF_ITEM_TYPES) {
- if (parser->song->tag == nullptr)
- parser->song->tag = new Tag();
- parser->song->tag->AddItem(parser->tag, text, text_len);
- }
-
- break;
-
- case XspfParser::LOCATION:
- if (parser->song == nullptr) {
- 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 != nullptr)
- parser->song->Free();
-}
-
-/*
- * The playlist object
- *
- */
-
-static SongEnumerator *
-xspf_open_stream(InputStream &is)
-{
- XspfParser parser;
- GMarkupParseContext *context;
- char buffer[1024];
- size_t nbytes;
- bool success;
- Error error2;
- GError *error = nullptr;
-
- /* parse the XSPF XML file */
-
- context = g_markup_parse_context_new(&xspf_parser,
- G_MARKUP_TREAT_CDATA_AS_TEXT,
- &parser, xspf_parser_destroy);
-
- while (true) {
- nbytes = is.LockRead(buffer, sizeof(buffer), error2);
- if (nbytes == 0) {
- if (error2.IsDefined()) {
- g_markup_parse_context_free(context);
- LogError(error2);
- return nullptr;
- }
-
- break;
- }
-
- success = g_markup_parse_context_parse(context, buffer, nbytes,
- &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
- }
-
- success = g_markup_parse_context_end_parse(context, &error);
- if (!success) {
- FormatError(xspf_domain,
- "XML parser failed: %s", error->message);
- g_error_free(error);
- g_markup_parse_context_free(context);
- return nullptr;
- }
-
- parser.songs.reverse();
- MemorySongEnumerator *playlist =
- new MemorySongEnumerator(std::move(parser.songs));
-
- g_markup_parse_context_free(context);
-
- return playlist;
-}
-
-static const char *const xspf_suffixes[] = {
- "xspf",
- nullptr
-};
-
-static const char *const xspf_mime_types[] = {
- "application/xspf+xml",
- nullptr
-};
-
-const struct playlist_plugin xspf_playlist_plugin = {
- "xspf",
-
- nullptr,
- nullptr,
- nullptr,
- xspf_open_stream,
-
- nullptr,
- xspf_suffixes,
- xspf_mime_types,
-};
diff --git a/src/playlist/XspfPlaylistPlugin.hxx b/src/playlist/XspfPlaylistPlugin.hxx
deleted file mode 100644
index fc9bbd2c6..000000000
--- a/src/playlist/XspfPlaylistPlugin.hxx
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX
-#define MPD_XSPF_PLAYLIST_PLUGIN_HXX
-
-extern const struct playlist_plugin xspf_playlist_plugin;
-
-#endif
diff --git a/src/playlist/cue/CueParser.cxx b/src/playlist/cue/CueParser.cxx
new file mode 100644
index 000000000..372c90b78
--- /dev/null
+++ b/src/playlist/cue/CueParser.cxx
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CueParser.hxx"
+#include "util/Alloc.hxx"
+#include "util/StringUtil.hxx"
+#include "util/CharUtil.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+CueParser::CueParser()
+ :state(HEADER),
+ current(nullptr),
+ previous(nullptr),
+ finished(nullptr),
+ end(false) {}
+
+CueParser::~CueParser()
+{
+ delete current;
+ delete previous;
+ delete finished;
+}
+
+static const char *
+cue_next_word(char *p, char **pp)
+{
+ assert(p >= *pp);
+ assert(!IsWhitespaceNotNull(*p));
+
+ const char *word = p;
+ while (!IsWhitespaceOrNull(*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 = StripLeft(*pp);
+ if (*p == 0)
+ return nullptr;
+
+ return cue_next_word(p, pp);
+}
+
+static const char *
+cue_next_value(char **pp)
+{
+ char *p = StripLeft(*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(TagBuilder &tag, TagType type, char *p)
+{
+ const char *value = cue_next_value(&p);
+ if (value != nullptr)
+ tag.AddItem(type, value);
+
+}
+
+static void
+cue_parse_rem(char *p, TagBuilder &tag)
+{
+ const char *type = cue_next_token(&p);
+ if (type == nullptr)
+ return;
+
+ TagType type2 = tag_name_parse_i(type);
+ if (type2 != TAG_NUM_OF_ITEM_TYPES)
+ cue_add_tag(tag, type2, p);
+}
+
+TagBuilder *
+CueParser::GetCurrentTag()
+{
+ if (state == HEADER)
+ return &header_tag;
+ else if (state == TRACK)
+ return &song_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;
+
+ assert(!current->GetTag().IsDefined());
+ current->SetTag(song_tag.Commit());
+
+ 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) {
+ TagBuilder *tag = GetCurrentTag();
+ if (tag != nullptr)
+ cue_parse_rem(p, *tag);
+ } else if (strcmp(command, "PERFORMER") == 0) {
+ /* MPD knows a "performer" tag, but it is not a good
+ match for this CUE tag; from the Hydrogenaudio
+ Knowledgebase: "At top-level this will specify the
+ CD artist, while at track-level it specifies the
+ track artist." */
+
+ TagType type = state == TRACK
+ ? TAG_ARTIST
+ : TAG_ALBUM_ARTIST;
+
+ TagBuilder *tag = GetCurrentTag();
+ if (tag != nullptr)
+ cue_add_tag(*tag, type, p);
+ } else if (strcmp(command, "TITLE") == 0) {
+ if (state == HEADER)
+ cue_add_tag(header_tag, TAG_ALBUM, p);
+ else if (state == TRACK)
+ cue_add_tag(song_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;
+ filename = 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 = new DetachedSong(filename);
+ assert(!current->GetTag().IsDefined());
+
+ song_tag = header_tag;
+ song_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->GetStartTime().ToMS() < (unsigned)position_ms) {
+ last_updated = true;
+ previous->SetEndTime(SongTime::FromMS(position_ms));
+ }
+
+ current->SetStartTime(SongTime::FromMS(position_ms));
+ }
+}
+
+void
+CueParser::Feed(const char *line)
+{
+ assert(!end);
+ assert(line != nullptr);
+
+ char *allocated = xstrdup(line);
+ Feed2(allocated);
+ free(allocated);
+}
+
+void
+CueParser::Finish()
+{
+ if (end)
+ /* has already been called, ignore */
+ return;
+
+ Commit();
+ end = true;
+}
+
+DetachedSong *
+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;
+ }
+
+ DetachedSong *song = finished;
+ finished = nullptr;
+ return song;
+}
diff --git a/src/playlist/cue/CueParser.hxx b/src/playlist/cue/CueParser.hxx
new file mode 100644
index 000000000..7e040169b
--- /dev/null
+++ b/src/playlist/cue/CueParser.hxx
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CUE_PARSER_HXX
+#define MPD_CUE_PARSER_HXX
+
+#include "check.h"
+#include "tag/TagBuilder.hxx"
+#include "Compiler.h"
+
+#include <string>
+
+class DetachedSong;
+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;
+
+ /**
+ * Tags read from the CUE header.
+ */
+ TagBuilder header_tag;
+
+ /**
+ * Tags read for the current song (attribute #current). When
+ * #current gets moved to #previous, TagBuilder::Commit() will
+ * be called.
+ */
+ TagBuilder song_tag;
+
+ std::string filename;
+
+ /**
+ * The song currently being edited.
+ */
+ DetachedSong *current;
+
+ /**
+ * The previous song. It is remembered because its end_time
+ * will be set to the current song's start time.
+ */
+ DetachedSong *previous;
+
+ /**
+ * A song that is completely finished and can be returned to
+ * the caller via cue_parser_get().
+ */
+ DetachedSong *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
+ */
+ DetachedSong *Get();
+
+private:
+ gcc_pure
+ TagBuilder *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/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx
new file mode 100644
index 000000000..3185a8144
--- /dev/null
+++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AsxPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * 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<DetachedSong> 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.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "ref" element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ AsxParser()
+ :state(ROOT) {}
+
+};
+
+static void XMLCALL
+asx_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "entry")) {
+ parser->state = AsxParser::ENTRY;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case AsxParser::ENTRY:
+ if (StringEqualsCaseASCII(element_name, "ref")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "href");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "author"))
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag_type = TAG_ARTIST;
+ else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void XMLCALL
+asx_end_element(void *user_data, const XML_Char *element_name)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (StringEqualsCaseASCII(element_name, "entry")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = AsxParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+asx_char_data(void *user_data, const XML_Char *s, int len)
+{
+ AsxParser *parser = (AsxParser *)user_data;
+
+ switch (parser->state) {
+ case AsxParser::ROOT:
+ break;
+
+ case AsxParser::ENTRY:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+asx_open_stream(InputStream &is)
+{
+ AsxParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(asx_start_element, asx_end_element);
+ expat.SetCharacterDataHandler(asx_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ nullptr
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ nullptr
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ "asx",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ asx_open_stream,
+
+ nullptr,
+ asx_suffixes,
+ asx_mime_types,
+};
diff --git a/src/playlist/plugins/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx
new file mode 100644
index 000000000..63371be0f
--- /dev/null
+++ b/src/playlist/plugins/AsxPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ASX_PLAYLIST_PLUGIN_HXX
+#define MPD_ASX_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx
new file mode 100644
index 000000000..b907d34d0
--- /dev/null
+++ b/src/playlist/plugins/CuePlaylistPlugin.cxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CuePlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "input/TextInputStream.hxx"
+
+#include <string>
+
+class CuePlaylist final : public SongEnumerator {
+ InputStream &is;
+ TextInputStream tis;
+ CueParser parser;
+
+ public:
+ CuePlaylist(InputStream &_is)
+ :is(_is), tis(is) {
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+cue_playlist_open_stream(InputStream &is)
+{
+ return new CuePlaylist(is);
+}
+
+DetachedSong *
+CuePlaylist::NextSong()
+{
+ DetachedSong *song = parser.Get();
+ if (song != nullptr)
+ return song;
+
+ const char *line;
+ while ((line = tis.ReadLine()) != nullptr) {
+ parser.Feed(line);
+ song = parser.Get();
+ if (song != nullptr)
+ return song;
+ }
+
+ parser.Finish();
+ return parser.Get();
+}
+
+static const char *const cue_playlist_suffixes[] = {
+ "cue",
+ nullptr
+};
+
+static const char *const cue_playlist_mime_types[] = {
+ "application/x-cue",
+ nullptr
+};
+
+const struct playlist_plugin cue_playlist_plugin = {
+ "cue",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ cue_playlist_open_stream,
+
+ nullptr,
+ cue_playlist_suffixes,
+ cue_playlist_mime_types,
+};
diff --git a/src/playlist/plugins/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx
new file mode 100644
index 000000000..4d833bfc2
--- /dev/null
+++ b/src/playlist/plugins/CuePlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CUE_PLAYLIST_PLUGIN_HXX
+#define MPD_CUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin cue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/DespotifyPlaylistPlugin.cxx b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx
new file mode 100644
index 000000000..636f64bc6
--- /dev/null
+++ b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DespotifyPlaylistPlugin.hxx"
+#include "lib/despotify/DespotifyUtils.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/Tag.hxx"
+#include "DetachedSong.hxx"
+#include "Log.hxx"
+
+extern "C" {
+#include <despotify.h>
+}
+
+#include <string.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void
+add_song(std::forward_list<DetachedSong> &songs, ds_track &track)
+{
+ const char *dsp_scheme = despotify_playlist_plugin.schemes[0];
+ char uri[128];
+ char *ds_uri;
+
+ /* Create a spt://... URI for MPD */
+ 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 */
+ FormatDebug(despotify_domain,
+ "Can't add track %s", track.title);
+ return;
+ }
+
+ songs.emplace_front(uri, mpd_despotify_tag_from_track(track));
+}
+
+static bool
+parse_track(struct despotify_session *session,
+ std::forward_list<DetachedSong> &songs,
+ struct ds_link *link)
+{
+ struct ds_track *track = despotify_link_get_track(session, link);
+ if (track == nullptr)
+ return false;
+
+ add_song(songs, *track);
+ return true;
+}
+
+static bool
+parse_playlist(struct despotify_session *session,
+ std::forward_list<DetachedSong> &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 SongEnumerator *
+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) {
+ FormatDebug(despotify_domain, "Can't find %s\n", url);
+ return nullptr;
+ }
+
+ std::forward_list<DetachedSong> 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 MemorySongEnumerator(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,
+
+ despotify_schemes,
+ nullptr,
+ nullptr,
+};
diff --git a/src/playlist/plugins/DespotifyPlaylistPlugin.hxx b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx
new file mode 100644
index 000000000..6acfd40f4
--- /dev/null
+++ b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/plugins/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
new file mode 100644
index 000000000..8baa11c03
--- /dev/null
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \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 "../SongEnumerator.hxx"
+#include "../cue/CueParser.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagId3.hxx"
+#include "tag/ApeTag.hxx"
+#include "DetachedSong.hxx"
+#include "TagFile.hxx"
+#include "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/ASCII.hxx"
+
+#include <string.h>
+
+class EmbeddedCuePlaylist final : public SongEnumerator {
+public:
+ /**
+ * This is an override for the CUE's "FILE". An embedded CUE
+ * sheet must always point to the song file it is contained
+ * in.
+ */
+ std::string filename;
+
+ /**
+ * The value of the file's "CUESHEET" tag.
+ */
+ std::string cuesheet;
+
+ /**
+ * The offset of the next line within "cuesheet".
+ */
+ char *next;
+
+ CueParser *parser;
+
+public:
+ EmbeddedCuePlaylist()
+ :parser(nullptr) {
+ }
+
+ virtual ~EmbeddedCuePlaylist() {
+ delete parser;
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static void
+embcue_tag_pair(const char *name, const char *value, void *ctx)
+{
+ EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx;
+
+ if (playlist->cuesheet.empty() &&
+ StringEqualsCaseASCII(name, "cuesheet"))
+ playlist->cuesheet = value;
+}
+
+static const struct tag_handler embcue_tag_handler = {
+ nullptr,
+ nullptr,
+ embcue_tag_pair,
+};
+
+static SongEnumerator *
+embcue_playlist_open_uri(const char *uri,
+ gcc_unused Mutex &mutex,
+ gcc_unused Cond &cond)
+{
+ if (!PathTraitsUTF8::IsAbsolute(uri))
+ /* only local files supported */
+ return nullptr;
+
+ const auto path_fs = AllocatedPath::FromUTF8(uri);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ const auto playlist = new EmbeddedCuePlaylist();
+
+ tag_file_scan(path_fs, embcue_tag_handler, playlist);
+ if (playlist->cuesheet.empty()) {
+ tag_ape_scan2(path_fs, &embcue_tag_handler, playlist);
+ if (playlist->cuesheet.empty())
+ tag_id3_scan(path_fs, &embcue_tag_handler, playlist);
+ }
+
+ if (playlist->cuesheet.empty()) {
+ /* no "CUESHEET" tag found */
+ delete playlist;
+ return nullptr;
+ }
+
+ playlist->filename = PathTraitsUTF8::GetBase(uri);
+
+ playlist->next = &playlist->cuesheet[0];
+ playlist->parser = new CueParser();
+
+ return playlist;
+}
+
+DetachedSong *
+EmbeddedCuePlaylist::NextSong()
+{
+ DetachedSong *song = parser->Get();
+ if (song != nullptr)
+ return song;
+
+ while (*next != 0) {
+ const char *line = next;
+ char *eol = strpbrk(next, "\r\n");
+ if (eol != nullptr) {
+ /* null-terminate the line */
+ *eol = 0;
+ next = eol + 1;
+ } else
+ /* last line; put the "next" pointer to the
+ end of the buffer */
+ next += strlen(line);
+
+ parser->Feed(line);
+ song = parser->Get();
+ if (song != nullptr) {
+ song->SetURI(filename);
+ return song;
+ }
+ }
+
+ parser->Finish();
+ song = parser->Get();
+ if (song != nullptr)
+ song->SetURI(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",
+ nullptr
+};
+
+const struct playlist_plugin embcue_playlist_plugin = {
+ "embcue",
+
+ nullptr,
+ nullptr,
+ embcue_playlist_open_uri,
+ nullptr,
+
+ nullptr,
+ embcue_playlist_suffixes,
+ nullptr,
+};
diff --git a/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
new file mode 100644
index 000000000..5eedf3f13
--- /dev/null
+++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+#define MPD_EMBCUE_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin embcue_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..93316ca6c
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ExtM3uPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
+#include "input/TextInputStream.hxx"
+
+#include <string.h>
+#include <stdlib.h>
+
+class ExtM3uPlaylist final : public SongEnumerator {
+ TextInputStream tis;
+
+public:
+ ExtM3uPlaylist(InputStream &is)
+ :tis(is) {
+ }
+
+ bool CheckFirstLine() {
+ char *line = tis.ReadLine();
+ if (line == nullptr)
+ return false;
+
+ StripRight(line);
+ return strcmp(line, "#EXTM3U") == 0;
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+extm3u_open_stream(InputStream &is)
+{
+ ExtM3uPlaylist *playlist = new ExtM3uPlaylist(is);
+
+ if (!playlist->CheckFirstLine()) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ delete playlist;
+ return nullptr;
+ }
+
+ return 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;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return Tag();
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = StripLeft(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return Tag();
+
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromS(unsigned(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.Commit();
+}
+
+DetachedSong *
+ExtM3uPlaylist::NextSong()
+{
+ Tag tag;
+ char *line_s;
+
+ do {
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
+
+ StripRight(line_s);
+
+ if (StringStartsWith(line_s, "#EXTINF:")) {
+ tag = extm3u_parse_tag(line_s + 8);
+ continue;
+ }
+
+ line_s = StripLeft(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s, std::move(tag));
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ "m3u8",
+ nullptr
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ nullptr
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ "extm3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ extm3u_open_stream,
+
+ nullptr,
+ extm3u_suffixes,
+ extm3u_mime_types,
+};
diff --git a/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..5743ded43
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+#define MPD_EXTM3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx
new file mode 100644
index 000000000..0428d291a
--- /dev/null
+++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "M3uPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "util/StringUtil.hxx"
+#include "input/TextInputStream.hxx"
+
+class M3uPlaylist final : public SongEnumerator {
+ TextInputStream tis;
+
+public:
+ M3uPlaylist(InputStream &is)
+ :tis(is) {
+ }
+
+ virtual DetachedSong *NextSong() override;
+};
+
+static SongEnumerator *
+m3u_open_stream(InputStream &is)
+{
+ return new M3uPlaylist(is);
+}
+
+DetachedSong *
+M3uPlaylist::NextSong()
+{
+ char *line_s;
+
+ do {
+ line_s = tis.ReadLine();
+ if (line_s == nullptr)
+ return nullptr;
+
+ line_s = Strip(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ "m3u8",
+ nullptr
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ nullptr
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ "m3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ m3u_open_stream,
+
+ nullptr,
+ m3u_suffixes,
+ m3u_mime_types,
+};
diff --git a/src/playlist/plugins/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx
new file mode 100644
index 000000000..f1ad14069
--- /dev/null
+++ b/src/playlist/plugins/M3uPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_M3U_PLAYLIST_PLUGIN_HXX
+#define MPD_M3U_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx
new file mode 100644
index 000000000..f7724f522
--- /dev/null
+++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PlsPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "input/InputStream.hxx"
+#include "DetachedSong.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+
+#include <string>
+#include <stdio.h>
+
+#include <stdio.h>
+
+static constexpr Domain pls_domain("pls");
+
+static void
+pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
+{
+ gchar *value;
+ GError *error = nullptr;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ FormatError(pls_domain,
+ "Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = nullptr;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = nullptr;
+ }
+ }
+
+ for (; num_entries > 0; --num_entries) {
+ char key[64];
+ sprintf(key, "File%u", num_entries);
+ char *uri = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
+ key, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ TagBuilder tag;
+
+ sprintf(key, "Title%u", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ nullptr);
+ if (value != nullptr)
+ tag.AddItem(TAG_TITLE, value);
+
+ g_free(value);
+
+ sprintf(key, "Length%u", num_entries);
+ int length = g_key_file_get_integer(keyfile, "playlist", key,
+ nullptr);
+ if (length > 0)
+ tag.SetDuration(SignedSongTime::FromS(length));
+
+ songs.emplace_front(uri, tag.Commit());
+ g_free(uri);
+ }
+
+}
+
+static SongEnumerator *
+pls_open_stream(InputStream &is)
+{
+ GError *error = nullptr;
+ Error error2;
+
+ std::string kf_data;
+
+ do {
+ char buffer[1024];
+ size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
+ if (nbytes == 0) {
+ if (error2.IsDefined()) {
+ LogError(error2);
+ return nullptr;
+ }
+
+ break;
+ }
+
+ kf_data.append(buffer, nbytes);
+ /* Limit to 64k */
+ } while (kf_data.length() < 65536);
+
+ if (kf_data.empty()) {
+ LogWarning(pls_domain, "KeyFile parser failed: No Data");
+ return nullptr;
+ }
+
+ GKeyFile *keyfile = g_key_file_new();
+ if (!g_key_file_load_from_data(keyfile,
+ kf_data.data(), kf_data.length(),
+ G_KEY_FILE_NONE, &error)) {
+ FormatError(pls_domain,
+ "KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return nullptr;
+ }
+
+ std::forward_list<DetachedSong> songs;
+ pls_parser(keyfile, songs);
+ g_key_file_free(keyfile);
+
+ return new MemorySongEnumerator(std::move(songs));
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ nullptr
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ nullptr
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ "pls",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ pls_open_stream,
+
+ nullptr,
+ pls_suffixes,
+ pls_mime_types,
+};
diff --git a/src/playlist/plugins/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx
new file mode 100644
index 000000000..1a3f33873
--- /dev/null
+++ b/src/playlist/plugins/PlsPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLS_PLAYLIST_PLUGIN_HXX
+#define MPD_PLS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
new file mode 100644
index 000000000..6f9aad54b
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "RssPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+/**
+ * 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<DetachedSong> 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.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "enclosure"
+ * element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ RssParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+rss_start_element(void *user_data, const XML_Char *element_name,
+ const XML_Char **atts)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ parser->state = RssParser::ITEM;
+ parser->location.clear();
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "enclosure")) {
+ const char *href =
+ ExpatParser::GetAttributeCase(atts, "url");
+ if (href != nullptr)
+ parser->location = href;
+ } else if (StringEqualsCaseASCII(element_name, "title"))
+ parser->tag_type = TAG_TITLE;
+ else if (StringEqualsCaseASCII(element_name, "itunes:author"))
+ parser->tag_type = TAG_ARTIST;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_end_element(void *user_data, const XML_Char *element_name)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (StringEqualsCaseASCII(element_name, "item")) {
+ if (!parser->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = RssParser::ROOT;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void XMLCALL
+rss_char_data(void *user_data, const XML_Char *s, int len)
+{
+ RssParser *parser = (RssParser *)user_data;
+
+ switch (parser->state) {
+ case RssParser::ROOT:
+ break;
+
+ case RssParser::ITEM:
+ if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+rss_open_stream(InputStream &is)
+{
+ RssParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(rss_start_element, rss_end_element);
+ expat.SetCharacterDataHandler(rss_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const rss_suffixes[] = {
+ "rss",
+ nullptr
+};
+
+static const char *const rss_mime_types[] = {
+ "application/rss+xml",
+ "text/xml",
+ nullptr
+};
+
+const struct playlist_plugin rss_playlist_plugin = {
+ "rss",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ rss_open_stream,
+
+ nullptr,
+ rss_suffixes,
+ rss_mime_types,
+};
diff --git a/src/playlist/plugins/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx
new file mode 100644
index 000000000..a00a5a898
--- /dev/null
+++ b/src/playlist/plugins/RssPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_RSS_PLAYLIST_PLUGIN_HXX
+#define MPD_RSS_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin rss_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
new file mode 100644
index 000000000..ec4d240a5
--- /dev/null
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SoundCloudPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "config/ConfigData.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <glib.h>
+#include <yajl/yajl_parse.h>
+
+#include <string>
+
+#include <string.h>
+
+static struct {
+ std::string apikey;
+} soundcloud_config;
+
+static constexpr Domain soundcloud_domain("soundcloud");
+
+static bool
+soundcloud_init(const config_param &param)
+{
+ // APIKEY for MPD application, registered under DarkFox' account.
+ soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8");
+ if (soundcloud_config.apikey.empty()) {
+ LogDebug(soundcloud_domain,
+ "disabling the soundcloud playlist plugin "
+ "because API key is not set");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * 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 (StringStartsWith(uri, "https://")) {
+ u = g_strdup(uri);
+ } else if (StringStartsWith(uri, "soundcloud.com")) {
+ u = g_strconcat("https://", uri, nullptr);
+ } else {
+ /* assume it's just a path on soundcloud.com */
+ u = g_strconcat("https://soundcloud.com/", uri, nullptr);
+ }
+
+ ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=",
+ u, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ 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",
+ nullptr,
+};
+
+struct parse_data {
+ int key;
+ char* stream_url;
+ long duration;
+ char* title;
+ int got_url; /* nesting level of last stream_url */
+
+ std::forward_list<DetachedSong> 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:
+ g_free(data->title);
+ data->title = g_strndup(s, stringlen);
+ break;
+ case Stream_URL:
+ 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 (memcmp((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;
+
+ char *u = g_strconcat(data->stream_url, "?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+
+ TagBuilder tag;
+ tag.SetDuration(SignedSongTime::FromMS(data->duration));
+ if (data->title != nullptr)
+ tag.AddItem(TAG_NAME, data->title);
+
+ data->songs.emplace_front(u, tag.Commit());
+ g_free(u);
+
+ return 1;
+}
+
+static yajl_callbacks parse_callbacks = {
+ nullptr,
+ nullptr,
+ handle_integer,
+ nullptr,
+ nullptr,
+ handle_string,
+ handle_start_map,
+ handle_mapkey,
+ handle_end_map,
+ nullptr,
+ nullptr,
+};
+
+/**
+ * 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)
+{
+ Error error;
+ InputStream *input_stream = InputStream::OpenReady(url, mutex, cond,
+ error);
+ if (input_stream == nullptr) {
+ if (error.IsDefined())
+ LogError(error);
+ return -1;
+ }
+
+ mutex.lock();
+
+ yajl_status stat;
+ int done = 0;
+
+ while (!done) {
+ char buffer[4096];
+ unsigned char *ubuffer = (unsigned char *)buffer;
+ const size_t nbytes =
+ input_stream->Read(buffer, sizeof(buffer), error);
+ if (nbytes == 0) {
+ if (error.IsDefined())
+ LogError(error);
+
+ if (input_stream->IsEOF()) {
+ done = true;
+ } else {
+ mutex.unlock();
+ delete 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);
+ LogError(soundcloud_domain, (const char *)str);
+ yajl_free_error(hand, str);
+ break;
+ }
+ }
+
+ mutex.unlock();
+ delete 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 SongEnumerator *
+soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
+{
+ assert(memcmp(uri, "soundcloud://", 13) == 0);
+ uri += 13;
+
+ char *u = nullptr;
+ if (memcmp(uri, "track/", 6) == 0) {
+ const char *rest = uri + 6;
+ u = g_strconcat("https://api.soundcloud.com/tracks/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "playlist/", 9) == 0) {
+ const char *rest = uri + 9;
+ u = g_strconcat("https://api.soundcloud.com/playlists/",
+ rest, ".json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "user/", 5) == 0) {
+ const char *rest = uri + 5;
+ u = g_strconcat("https://api.soundcloud.com/users/",
+ rest, "/tracks.json?client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "search/", 7) == 0) {
+ const char *rest = uri + 7;
+ u = g_strconcat("https://api.soundcloud.com/tracks.json?q=",
+ rest, "&client_id=",
+ soundcloud_config.apikey.c_str(), nullptr);
+ } else if (memcmp(uri, "url/", 4) == 0) {
+ const char *rest = uri + 4;
+ /* Translate to soundcloud resolver call. libcurl will automatically
+ follow the redirect to the right resource. */
+ u = soundcloud_resolve(rest);
+ }
+
+ if (u == nullptr) {
+ LogWarning(soundcloud_domain, "unknown soundcloud URI");
+ return nullptr;
+ }
+
+ struct parse_data data;
+ data.got_url = 0;
+ data.title = nullptr;
+ data.stream_url = nullptr;
+#ifdef HAVE_YAJL1
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr,
+ &data);
+#else
+ yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
+#endif
+
+ int ret = soundcloud_parse_json(u, hand, mutex, cond);
+
+ g_free(u);
+ yajl_free(hand);
+ g_free(data.title);
+ g_free(data.stream_url);
+
+ if (ret == -1)
+ return nullptr;
+
+ data.songs.reverse();
+ return new MemorySongEnumerator(std::move(data.songs));
+}
+
+static const char *const soundcloud_schemes[] = {
+ "soundcloud",
+ nullptr
+};
+
+const struct playlist_plugin soundcloud_playlist_plugin = {
+ "soundcloud",
+
+ soundcloud_init,
+ nullptr,
+ soundcloud_open_uri,
+ nullptr,
+
+ soundcloud_schemes,
+ nullptr,
+ nullptr,
+};
+
+
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
new file mode 100644
index 000000000..b355b477a
--- /dev/null
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+#define MPD_SOUNDCLOUD_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin soundcloud_playlist_plugin;
+
+#endif
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx
new file mode 100644
index 000000000..5b6010b53
--- /dev/null
+++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "XspfPlaylistPlugin.hxx"
+#include "../PlaylistPlugin.hxx"
+#include "../MemorySongEnumerator.hxx"
+#include "DetachedSong.hxx"
+#include "input/InputStream.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "lib/expat/ExpatParser.hxx"
+#include "Log.hxx"
+
+#include <string.h>
+
+static constexpr Domain xspf_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<DetachedSong> 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.
+ */
+ TagType tag_type;
+
+ /**
+ * The current song URI. It is set by the "location" element.
+ */
+ std::string location;
+
+ TagBuilder tag_builder;
+
+ XspfParser()
+ :state(ROOT) {}
+};
+
+static void XMLCALL
+xspf_start_element(void *user_data, const XML_Char *element_name,
+ gcc_unused const XML_Char **atts)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ 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->location.clear();
+ parser->tag_type = 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_type = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag_type = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag_type = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag_type = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag_type = TAG_TRACK;
+
+ break;
+
+ case XspfParser::LOCATION:
+ break;
+ }
+}
+
+static void XMLCALL
+xspf_end_element(void *user_data, const XML_Char *element_name)
+{
+ 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->location.empty())
+ parser->songs.emplace_front(std::move(parser->location),
+ parser->tag_builder.Commit());
+
+ parser->state = XspfParser::TRACKLIST;
+ } else
+ parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->state = XspfParser::TRACK;
+ break;
+ }
+}
+
+static void XMLCALL
+xspf_char_data(void *user_data, const XML_Char *s, int len)
+{
+ XspfParser *parser = (XspfParser *)user_data;
+
+ switch (parser->state) {
+ case XspfParser::ROOT:
+ case XspfParser::PLAYLIST:
+ case XspfParser::TRACKLIST:
+ break;
+
+ case XspfParser::TRACK:
+ if (!parser->location.empty() &&
+ parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
+ parser->tag_builder.AddItem(parser->tag_type, s, len);
+
+ break;
+
+ case XspfParser::LOCATION:
+ parser->location.assign(s, len);
+
+ break;
+ }
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+static SongEnumerator *
+xspf_open_stream(InputStream &is)
+{
+ XspfParser parser;
+
+ {
+ ExpatParser expat(&parser);
+ expat.SetElementHandler(xspf_start_element, xspf_end_element);
+ expat.SetCharacterDataHandler(xspf_char_data);
+
+ Error error;
+ if (!expat.Parse(is, error)) {
+ LogError(error);
+ return nullptr;
+ }
+ }
+
+ parser.songs.reverse();
+ return new MemorySongEnumerator(std::move(parser.songs));
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ nullptr
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ nullptr
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ "xspf",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ xspf_open_stream,
+
+ nullptr,
+ xspf_suffixes,
+ xspf_mime_types,
+};
diff --git a/src/playlist/plugins/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx
new file mode 100644
index 000000000..6b08a6be6
--- /dev/null
+++ b/src/playlist/plugins/XspfPlaylistPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_XSPF_PLAYLIST_PLUGIN_HXX
+#define MPD_XSPF_PLAYLIST_PLUGIN_HXX
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif
diff --git a/src/poison.h b/src/poison.h
index c95b5d005..c112f6e19 100644
--- a/src/poison.h
+++ b/src/poison.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/protocol/Ack.cxx b/src/protocol/Ack.cxx
index 3b66764bf..56f0f0b5d 100644
--- a/src/protocol/Ack.cxx
+++ b/src/protocol/Ack.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/protocol/Ack.hxx b/src/protocol/Ack.hxx
index 7d3be2626..e2c4dd9d1 100644
--- a/src/protocol/Ack.hxx
+++ b/src/protocol/Ack.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx
index 86527c751..e373827b4 100644
--- a/src/protocol/ArgParser.cxx
+++ b/src/protocol/ArgParser.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,6 +20,7 @@
#include "config.h"
#include "ArgParser.hxx"
#include "Result.hxx"
+#include "Chrono.hxx"
#include <limits>
@@ -186,3 +187,25 @@ check_float(Client &client, float *value_r, const char *s)
*value_r = value;
return true;
}
+
+bool
+ParseCommandArg(Client &client, SongTime &value_r, const char *s)
+{
+ float value;
+ bool success = check_float(client, &value, s) && value >= 0;
+ if (success)
+ value_r = SongTime::FromS(value);
+
+ return success;
+}
+
+bool
+ParseCommandArg(Client &client, SignedSongTime &value_r, const char *s)
+{
+ float value;
+ bool success = check_float(client, &value, s);
+ if (success)
+ value_r = SignedSongTime::FromS(value);
+
+ return success;
+}
diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx
index ea28de79e..0f79e7ab2 100644
--- a/src/protocol/ArgParser.hxx
+++ b/src/protocol/ArgParser.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,8 @@
#include <stdint.h>
class Client;
+class SongTime;
+class SignedSongTime;
bool
check_uint32(Client &client, uint32_t *dst, const char *s);
@@ -45,4 +47,10 @@ check_bool(Client &client, bool *value_r, const char *s);
bool
check_float(Client &client, float *value_r, const char *s);
+bool
+ParseCommandArg(Client &client, SongTime &value_r, const char *s);
+
+bool
+ParseCommandArg(Client &client, SignedSongTime &value_r, const char *s);
+
#endif
diff --git a/src/protocol/Result.cxx b/src/protocol/Result.cxx
index 17b553c34..3cc5fc33e 100644
--- a/src/protocol/Result.cxx
+++ b/src/protocol/Result.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
#include "config.h"
#include "Result.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include <assert.h>
diff --git a/src/protocol/Result.hxx b/src/protocol/Result.hxx
index 0f7339c1c..0ac9d1e6b 100644
--- a/src/protocol/Result.hxx
+++ b/src/protocol/Result.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/queue/IdTable.hxx b/src/queue/IdTable.hxx
new file mode 100644
index 000000000..8e445243d
--- /dev/null
+++ b/src/queue/IdTable.hxx
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ID_TABLE_HXX
+#define MPD_ID_TABLE_HXX
+
+#include "Compiler.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_n(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/queue/Playlist.cxx b/src/queue/Playlist.cxx
new file mode 100644
index 000000000..b2fd673b4
--- /dev/null
+++ b/src/queue/Playlist.cxx
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "PlaylistError.hxx"
+#include "PlayerControl.hxx"
+#include "DetachedSong.hxx"
+#include "Idle.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+void
+playlist::TagModified(DetachedSong &&song)
+{
+ if (!playing)
+ return;
+
+ assert(current >= 0);
+
+ DetachedSong &current_song = queue.GetOrder(current);
+ if (song.IsSame(current_song))
+ current_song.MoveTagFrom(std::move(song));
+
+ queue.ModifyAtOrder(current);
+ queue.IncrementVersion();
+ idle_add(IDLE_PLAYLIST);
+}
+
+/**
+ * Queue a song, addressed by its order number.
+ */
+static void
+playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
+ unsigned order)
+{
+ assert(playlist.queue.IsValidOrder(order));
+
+ playlist.queued = order;
+
+ const DetachedSong &song = playlist.queue.GetOrder(order);
+
+ FormatDebug(playlist_domain, "queue song %i:\"%s\"",
+ playlist.queued, song.GetURI());
+
+ pc.EnqueueSong(new DetachedSong(song));
+}
+
+/**
+ * Called if the player thread has started playing the "queued" song.
+ */
+static void
+playlist_song_started(playlist &playlist, PlayerControl &pc)
+{
+ assert(pc.next_song == nullptr);
+ 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 DetachedSong *
+playlist::GetQueuedSong() const
+{
+ return playing && queued >= 0
+ ? &queue.GetOrder(queued)
+ : nullptr;
+}
+
+void
+playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
+{
+ if (!playing)
+ return;
+
+ if (prev == nullptr && bulk_edit)
+ /* postponed until CommitBulk() to avoid always
+ queueing the first song that is being added (in
+ random mode) */
+ return;
+
+ assert(!queue.IsEmpty());
+ assert((queued < 0) == (prev == nullptr));
+
+ 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 DetachedSong *const next_song = next_order >= 0
+ ? &queue.GetOrder(next_order)
+ : nullptr;
+
+ if (prev != nullptr && 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(PlayerControl &pc, int order)
+{
+ playing = true;
+ queued = -1;
+
+ const DetachedSong &song = queue.GetOrder(order);
+
+ FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI());
+
+ pc.Play(new DetachedSong(song));
+ current = order;
+}
+
+static void
+playlist_resume_playback(playlist &playlist, PlayerControl &pc);
+
+void
+playlist::SyncWithPlayer(PlayerControl &pc)
+{
+ if (!playing)
+ /* this event has reached us out of sync: we aren't
+ playing anymore; ignore the event */
+ return;
+
+ pc.Lock();
+ const PlayerState pc_state = pc.GetState();
+ const DetachedSong *pc_next_song = pc.next_song;
+ pc.Unlock();
+
+ if (pc_state == PlayerState::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(playlist &playlist, PlayerControl &pc)
+{
+ assert(playlist.playing);
+ assert(pc.GetState() == PlayerState::STOP);
+
+ const auto error = pc.GetErrorType();
+ if (error == PlayerError::NONE)
+ playlist.error_count = 0;
+ else
+ ++playlist.error_count;
+
+ if ((playlist.stop_on_error && error != PlayerError::NONE) ||
+ error == PlayerError::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(PlayerControl &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(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(PlayerControl &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(PlayerControl &pc, bool status)
+{
+ if (status == queue.random)
+ return;
+
+ const DetachedSong *const queued_song = GetQueuedSong();
+
+ queue.random = status;
+
+ if (queue.random) {
+ /* shuffle the queue order, but preserve current */
+
+ const int current_position = playing
+ ? GetCurrentPosition()
+ : -1;
+
+ 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/queue/Playlist.hxx b/src/queue/Playlist.hxx
new file mode 100644
index 000000000..ea19d9bba
--- /dev/null
+++ b/src/queue/Playlist.hxx
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PLAYLIST_HXX
+#define MPD_PLAYLIST_HXX
+
+#include "queue/Queue.hxx"
+#include "PlaylistError.hxx"
+
+enum TagType : uint8_t;
+struct PlayerControl;
+class DetachedSong;
+class Database;
+class Error;
+class SongLoader;
+class SongTime;
+class SignedSongTime;
+
+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;
+
+ /**
+ * If true, then a bulk edit has been initiated by
+ * BeginBulk(), and UpdateQueuedSong() and OnModified() will
+ * be postponed until CommitBulk()
+ */
+ bool bulk_edit;
+
+ /**
+ * Has the queue been modified during bulk edit mode?
+ */
+ bool bulk_modified;
+
+ /**
+ * 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),
+ bulk_edit(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 DetachedSong *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(PlayerControl &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(PlayerControl &pc, const DetachedSong *prev);
+
+public:
+ void BeginBulk();
+ void CommitBulk(PlayerControl &pc);
+
+ void Clear(PlayerControl &pc);
+
+ /**
+ * A tag in the play queue has been modified by the player
+ * thread. Apply the given song's tag to the current song if
+ * the song matches.
+ */
+ void TagModified(DetachedSong &&song);
+
+#ifdef ENABLE_DATABASE
+ /**
+ * The database has been modified. Pull all updates.
+ */
+ void DatabaseModified(const Database &db);
+#endif
+
+ /**
+ * @return the new song id or 0 on error
+ */
+ unsigned AppendSong(PlayerControl &pc,
+ DetachedSong &&song,
+ Error &error);
+
+ /**
+ * @return the new song id or 0 on error
+ */
+ unsigned AppendURI(PlayerControl &pc,
+ const SongLoader &loader,
+ const char *uri_utf8,
+ Error &error);
+
+protected:
+ void DeleteInternal(PlayerControl &pc,
+ unsigned song, const DetachedSong **queued_p);
+
+public:
+ PlaylistResult DeletePosition(PlayerControl &pc,
+ unsigned position);
+
+ PlaylistResult DeleteOrder(PlayerControl &pc,
+ unsigned order) {
+ return DeletePosition(pc, queue.OrderToPosition(order));
+ }
+
+ PlaylistResult DeleteId(PlayerControl &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
+ */
+ PlaylistResult DeleteRange(PlayerControl &pc,
+ unsigned start, unsigned end);
+
+ void DeleteSong(PlayerControl &pc, const char *uri);
+
+ void Shuffle(PlayerControl &pc, unsigned start, unsigned end);
+
+ PlaylistResult MoveRange(PlayerControl &pc,
+ unsigned start, unsigned end, int to);
+
+ PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to);
+
+ PlaylistResult SwapPositions(PlayerControl &pc,
+ unsigned song1, unsigned song2);
+
+ PlaylistResult SwapIds(PlayerControl &pc,
+ unsigned id1, unsigned id2);
+
+ PlaylistResult SetPriorityRange(PlayerControl &pc,
+ unsigned start_position,
+ unsigned end_position,
+ uint8_t priority);
+
+ PlaylistResult SetPriorityId(PlayerControl &pc,
+ unsigned song_id, uint8_t priority);
+
+ /**
+ * Sets the start_time and end_time attributes on the song
+ * with the specified id.
+ */
+ bool SetSongIdRange(PlayerControl &pc, unsigned id,
+ SongTime start, SongTime end,
+ Error &error);
+
+ bool AddSongIdTag(unsigned id, TagType tag_type, const char *value,
+ Error &error);
+ bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error);
+
+ void Stop(PlayerControl &pc);
+
+ PlaylistResult PlayPosition(PlayerControl &pc, int position);
+
+ void PlayOrder(PlayerControl &pc, int order);
+
+ PlaylistResult PlayId(PlayerControl &pc, int id);
+
+ void PlayNext(PlayerControl &pc);
+
+ void PlayPrevious(PlayerControl &pc);
+
+ PlaylistResult SeekSongOrder(PlayerControl &pc,
+ unsigned song_order,
+ SongTime seek_time);
+
+ PlaylistResult SeekSongPosition(PlayerControl &pc,
+ unsigned song_position,
+ SongTime seek_time);
+
+ PlaylistResult SeekSongId(PlayerControl &pc,
+ unsigned song_id, SongTime 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
+ */
+ PlaylistResult SeekCurrent(PlayerControl &pc,
+ SignedSongTime seek_time, bool relative);
+
+ bool GetRepeat() const {
+ return queue.repeat;
+ }
+
+ void SetRepeat(PlayerControl &pc, bool new_value);
+
+ bool GetRandom() const {
+ return queue.random;
+ }
+
+ void SetRandom(PlayerControl &pc, bool new_value);
+
+ bool GetSingle() const {
+ return queue.single;
+ }
+
+ void SetSingle(PlayerControl &pc, bool new_value);
+
+ bool GetConsume() const {
+ return queue.consume;
+ }
+
+ void SetConsume(bool new_value);
+};
+
+#endif
diff --git a/src/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx
new file mode 100644
index 000000000..f7e80dc46
--- /dev/null
+++ b/src/queue/PlaylistControl.cxx
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Functions for controlling playback on the playlist level.
+ *
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "PlaylistError.hxx"
+#include "PlayerControl.hxx"
+#include "DetachedSong.hxx"
+#include "Log.hxx"
+
+void
+playlist::Stop(PlayerControl &pc)
+{
+ if (!playing)
+ return;
+
+ assert(current >= 0);
+
+ FormatDebug(playlist_domain, "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);
+ }
+}
+
+PlaylistResult
+playlist::PlayPosition(PlayerControl &pc, int song)
+{
+ pc.ClearError();
+
+ unsigned i = song;
+ if (song == -1) {
+ /* play any song ("current" song, or the first song */
+
+ if (queue.IsEmpty())
+ return PlaylistResult::SUCCESS;
+
+ if (playing) {
+ /* already playing: unpause playback, just in
+ case it was paused, and return */
+ pc.SetPause(false);
+ return PlaylistResult::SUCCESS;
+ }
+
+ /* select a song: "current" song, or the first one */
+ i = current >= 0
+ ? current
+ : 0;
+ } else if (!queue.IsValidPosition(song))
+ return PlaylistResult::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 PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::PlayId(PlayerControl &pc, int id)
+{
+ if (id == -1)
+ return PlayPosition(pc, id);
+
+ int song = queue.IdToPosition(id);
+ if (song < 0)
+ return PlaylistResult::NO_SUCH_SONG;
+
+ return PlayPosition(pc, song);
+}
+
+void
+playlist::PlayNext(PlayerControl &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 PlayOrder() will
+ discard them anyway */
+ }
+
+ PlayOrder(pc, next_order);
+ }
+
+ /* Consume mode removes each played songs. */
+ if (queue.consume)
+ DeleteOrder(pc, old_current);
+}
+
+void
+playlist::PlayPrevious(PlayerControl &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);
+}
+
+PlaylistResult
+playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
+{
+ assert(queue.IsValidOrder(i));
+
+ const DetachedSong *queued_song = GetQueuedSong();
+
+ 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;
+ }
+
+ if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) {
+ UpdateQueuedSong(pc, queued_song);
+
+ return PlaylistResult::NOT_PLAYING;
+ }
+
+ queued = -1;
+ UpdateQueuedSong(pc, nullptr);
+
+ return PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::SeekSongPosition(PlayerControl &pc, unsigned song,
+ SongTime seek_time)
+{
+ if (!queue.IsValidPosition(song))
+ return PlaylistResult::BAD_RANGE;
+
+ unsigned i = queue.random
+ ? queue.PositionToOrder(song)
+ : song;
+
+ return SeekSongOrder(pc, i, seek_time);
+}
+
+PlaylistResult
+playlist::SeekSongId(PlayerControl &pc, unsigned id, SongTime seek_time)
+{
+ int song = queue.IdToPosition(id);
+ if (song < 0)
+ return PlaylistResult::NO_SUCH_SONG;
+
+ return SeekSongPosition(pc, song, seek_time);
+}
+
+PlaylistResult
+playlist::SeekCurrent(PlayerControl &pc,
+ SignedSongTime seek_time, bool relative)
+{
+ if (!playing)
+ return PlaylistResult::NOT_PLAYING;
+
+ if (relative) {
+ const auto status = pc.GetStatus();
+
+ if (status.state != PlayerState::PLAY &&
+ status.state != PlayerState::PAUSE)
+ return PlaylistResult::NOT_PLAYING;
+
+ seek_time += status.elapsed_time;
+ if (seek_time.IsNegative())
+ seek_time = SignedSongTime::zero();
+ }
+
+ if (seek_time.IsNegative())
+ seek_time = SignedSongTime::zero();
+
+ return SeekSongOrder(pc, current, SongTime(seek_time));
+}
diff --git a/src/queue/PlaylistEdit.cxx b/src/queue/PlaylistEdit.cxx
new file mode 100644
index 000000000..22a88dc46
--- /dev/null
+++ b/src/queue/PlaylistEdit.cxx
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Functions for editing the playlist (adding, removing, reordering
+ * songs in the queue).
+ *
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "PlaylistError.hxx"
+#include "PlayerControl.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
+#include "Idle.hxx"
+
+#include <stdlib.h>
+
+void
+playlist::OnModified()
+{
+ if (bulk_edit) {
+ /* postponed to CommitBulk() */
+ bulk_modified = true;
+ return;
+ }
+
+ queue.IncrementVersion();
+
+ idle_add(IDLE_PLAYLIST);
+}
+
+void
+playlist::Clear(PlayerControl &pc)
+{
+ Stop(pc);
+
+ queue.Clear();
+ current = -1;
+
+ OnModified();
+}
+
+void
+playlist::BeginBulk()
+{
+ assert(!bulk_edit);
+
+ bulk_edit = true;
+ bulk_modified = false;
+}
+
+void
+playlist::CommitBulk(PlayerControl &pc)
+{
+ assert(bulk_edit);
+
+ bulk_edit = false;
+ if (!bulk_modified)
+ return;
+
+ if (queued < 0)
+ /* if no song was queued, UpdateQueuedSong() is being
+ ignored in "bulk" edit mode; now that we have
+ shuffled all new songs, we can pick a random one
+ (instead of always picking the first one that was
+ added) */
+ UpdateQueuedSong(pc, nullptr);
+
+ OnModified();
+}
+
+unsigned
+playlist::AppendSong(PlayerControl &pc, DetachedSong &&song, Error &error)
+{
+ unsigned id;
+
+ if (queue.IsFull()) {
+ error.Set(playlist_domain, int(PlaylistResult::TOO_LARGE),
+ "Playlist is too large");
+ return 0;
+ }
+
+ const DetachedSong *const queued_song = GetQueuedSong();
+
+ id = queue.Append(std::move(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();
+
+ return id;
+}
+
+unsigned
+playlist::AppendURI(PlayerControl &pc, const SongLoader &loader,
+ const char *uri,
+ Error &error)
+{
+ DetachedSong *song = loader.LoadSong(uri, error);
+ if (song == nullptr)
+ return 0;
+
+ unsigned result = AppendSong(pc, std::move(*song), error);
+ delete song;
+
+ return result;
+}
+
+PlaylistResult
+playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2)
+{
+ if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
+ return PlaylistResult::BAD_RANGE;
+
+ const DetachedSong *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 PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2)
+{
+ int song1 = queue.IdToPosition(id1);
+ int song2 = queue.IdToPosition(id2);
+
+ if (song1 < 0 || song2 < 0)
+ return PlaylistResult::NO_SUCH_SONG;
+
+ return SwapPositions(pc, song1, song2);
+}
+
+PlaylistResult
+playlist::SetPriorityRange(PlayerControl &pc,
+ unsigned start, unsigned end,
+ uint8_t priority)
+{
+ if (start >= GetLength())
+ return PlaylistResult::BAD_RANGE;
+
+ if (end > GetLength())
+ end = GetLength();
+
+ if (start >= end)
+ return PlaylistResult::SUCCESS;
+
+ /* remember "current" and "queued" */
+
+ const int current_position = GetCurrentPosition();
+ const DetachedSong *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 PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::SetPriorityId(PlayerControl &pc,
+ unsigned song_id, uint8_t priority)
+{
+ int song_position = queue.IdToPosition(song_id);
+ if (song_position < 0)
+ return PlaylistResult::NO_SUCH_SONG;
+
+ return SetPriorityRange(pc, song_position, song_position + 1,
+ priority);
+
+}
+
+void
+playlist::DeleteInternal(PlayerControl &pc,
+ unsigned song, const DetachedSong **queued_p)
+{
+ assert(song < GetLength());
+
+ unsigned songOrder = queue.PositionToOrder(song);
+
+ if (playing && current == (int)songOrder) {
+ const bool paused = pc.GetState() == PlayerState::PAUSE;
+
+ /* the current song is going to be deleted: 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 {
+ /* stop the player */
+
+ pc.Stop();
+ playing = false;
+ }
+
+ *queued_p = nullptr;
+ } 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--;
+}
+
+PlaylistResult
+playlist::DeletePosition(PlayerControl &pc, unsigned song)
+{
+ if (song >= queue.GetLength())
+ return PlaylistResult::BAD_RANGE;
+
+ const DetachedSong *queued_song = GetQueuedSong();
+
+ DeleteInternal(pc, song, &queued_song);
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end)
+{
+ if (start >= queue.GetLength())
+ return PlaylistResult::BAD_RANGE;
+
+ if (end > queue.GetLength())
+ end = queue.GetLength();
+
+ if (start >= end)
+ return PlaylistResult::SUCCESS;
+
+ const DetachedSong *queued_song = GetQueuedSong();
+
+ do {
+ DeleteInternal(pc, --end, &queued_song);
+ } while (end != start);
+
+ UpdateQueuedSong(pc, queued_song);
+ OnModified();
+
+ return PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::DeleteId(PlayerControl &pc, unsigned id)
+{
+ int song = queue.IdToPosition(id);
+ if (song < 0)
+ return PlaylistResult::NO_SUCH_SONG;
+
+ return DeletePosition(pc, song);
+}
+
+void
+playlist::DeleteSong(PlayerControl &pc, const char *uri)
+{
+ for (int i = queue.GetLength() - 1; i >= 0; --i)
+ if (queue.Get(i).IsURI(uri))
+ DeletePosition(pc, i);
+}
+
+PlaylistResult
+playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
+{
+ if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1))
+ return PlaylistResult::BAD_RANGE;
+
+ if ((to >= 0 && to + end - start - 1 >= GetLength()) ||
+ (to < 0 && unsigned(abs(to)) > GetLength()))
+ return PlaylistResult::BAD_RANGE;
+
+ if ((int)start == to)
+ /* nothing happens */
+ return PlaylistResult::SUCCESS;
+
+ const DetachedSong *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 PlaylistResult::BAD_RANGE;
+
+ if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
+ /* no-op, can't be moved to offset of itself */
+ return PlaylistResult::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 PlaylistResult::SUCCESS;
+}
+
+PlaylistResult
+playlist::MoveId(PlayerControl &pc, unsigned id1, int to)
+{
+ int song = queue.IdToPosition(id1);
+ if (song < 0)
+ return PlaylistResult::NO_SUCH_SONG;
+
+ return MoveRange(pc, song, song + 1, to);
+}
+
+void
+playlist::Shuffle(PlayerControl &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 DetachedSong *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();
+}
+
+bool
+playlist::SetSongIdRange(PlayerControl &pc, unsigned id,
+ SongTime start, SongTime end,
+ Error &error)
+{
+ assert(end.IsZero() || start < end);
+
+ int position = queue.IdToPosition(id);
+ if (position < 0) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No such song");
+ return false;
+ }
+
+ if (playing) {
+ if (position == current) {
+ error.Set(playlist_domain, int(PlaylistResult::DENIED),
+ "Cannot edit the current song");
+ return false;
+ }
+
+ if (position == queued) {
+ /* if we're manipulating the "queued" song,
+ the decoder thread may be decoding it
+ already; cancel that */
+ pc.Cancel();
+ queued = -1;
+ }
+ }
+
+ DetachedSong &song = queue.Get(position);
+
+ const auto duration = song.GetTag().duration;
+ if (!duration.IsNegative()) {
+ /* validate the offsets */
+
+ if (start > duration) {
+ error.Set(playlist_domain,
+ int(PlaylistResult::BAD_RANGE),
+ "Invalid start offset");
+ return false;
+ }
+
+ if (end >= duration)
+ end = SongTime::zero();
+ }
+
+ /* edit it */
+ song.SetStartTime(start);
+ song.SetEndTime(end);
+
+ /* announce the change to all interested subsystems */
+ UpdateQueuedSong(pc, nullptr);
+ queue.ModifyAtPosition(position);
+ OnModified();
+ return true;
+}
diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx
new file mode 100644
index 000000000..6ea86166e
--- /dev/null
+++ b/src/queue/PlaylistState.cxx
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Saving and loading the playlist to/from the state file.
+ *
+ */
+
+#include "config.h"
+#include "PlaylistState.hxx"
+#include "PlaylistError.hxx"
+#include "Playlist.hxx"
+#include "queue/QueueSave.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "PlayerControl.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "fs/Limits.hxx"
+#include "util/CharUtil.hxx"
+#include "util/StringUtil.hxx"
+#include "Log.hxx"
+
+#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(BufferedOutputStream &os, const struct playlist &playlist,
+ PlayerControl &pc)
+{
+ const auto player_status = pc.GetStatus();
+
+ os.Write(PLAYLIST_STATE_FILE_STATE);
+
+ if (playlist.playing) {
+ switch (player_status.state) {
+ case PlayerState::PAUSE:
+ os.Write(PLAYLIST_STATE_FILE_STATE_PAUSE "\n");
+ break;
+ default:
+ os.Write(PLAYLIST_STATE_FILE_STATE_PLAY "\n");
+ }
+ os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n",
+ playlist.queue.OrderToPosition(playlist.current));
+ os.Format(PLAYLIST_STATE_FILE_TIME "%f\n",
+ player_status.elapsed_time.ToDoubleS());
+ } else {
+ os.Write(PLAYLIST_STATE_FILE_STATE_STOP "\n");
+
+ if (playlist.current >= 0)
+ os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n",
+ playlist.queue.OrderToPosition(playlist.current));
+ }
+
+ os.Format(PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random);
+ os.Format(PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat);
+ os.Format(PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single);
+ os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume);
+ os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
+ (int)pc.GetCrossFade());
+ os.Format(PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc.GetMixRampDb());
+ os.Format(PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
+ pc.GetMixRampDelay());
+ os.Write(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n");
+ queue_save(os, playlist.queue);
+ os.Write(PLAYLIST_STATE_FILE_PLAYLIST_END "\n");
+}
+
+static void
+playlist_state_load(TextFile &file, const SongLoader &song_loader,
+ struct playlist &playlist)
+{
+ const char *line = file.ReadLine();
+ if (line == nullptr) {
+ LogWarning(playlist_domain, "No playlist in state file");
+ return;
+ }
+
+ while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
+ queue_load_song(file, song_loader, line, playlist.queue);
+
+ line = file.ReadLine();
+ if (line == nullptr) {
+ LogWarning(playlist_domain,
+ "'" PLAYLIST_STATE_FILE_PLAYLIST_END
+ "' not found in state file");
+ break;
+ }
+ }
+
+ playlist.queue.IncrementVersion();
+}
+
+bool
+playlist_state_restore(const char *line, TextFile &file,
+ const SongLoader &song_loader,
+ struct playlist &playlist, PlayerControl &pc)
+{
+ int current = -1;
+ SongTime seek_time = SongTime::zero();
+ bool random_mode = false;
+
+ if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE))
+ return false;
+
+ line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
+
+ PlayerState state;
+ if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
+ state = PlayerState::PLAY;
+ else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
+ state = PlayerState::PAUSE;
+ else
+ state = PlayerState::STOP;
+
+ while ((line = file.ReadLine()) != nullptr) {
+ if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) {
+ double seconds = atof(line + strlen(PLAYLIST_STATE_FILE_TIME));
+ seek_time = SongTime::FromS(seconds);
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) {
+ playlist.SetRepeat(pc,
+ strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
+ "1") == 0);
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) {
+ playlist.SetSingle(pc,
+ strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
+ "1") == 0);
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) {
+ playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
+ "1") == 0);
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) {
+ pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
+ pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
+ const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY);
+
+ /* this check discards "nan" which was used
+ prior to MPD 0.18 */
+ if (IsDigitASCII(*p))
+ pc.SetMixRampDelay(atof(p));
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) {
+ random_mode =
+ strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM),
+ "1") == 0;
+ } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) {
+ current = atoi(&(line
+ [strlen
+ (PLAYLIST_STATE_FILE_CURRENT)]));
+ } else if (StringStartsWith(line,
+ PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
+ playlist_state_load(file, song_loader, playlist);
+ }
+ }
+
+ playlist.SetRandom(pc, random_mode);
+
+ if (!playlist.queue.IsEmpty()) {
+ if (!playlist.queue.IsValidPosition(current))
+ current = 0;
+
+ if (state == PlayerState::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 = PlayerState::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 != PlayerState::STOP)
+ pc.UpdateAudio();
+
+ if (state == PlayerState::STOP /* && config_option */)
+ playlist.current = current;
+ else if (seek_time.count() == 0)
+ playlist.PlayPosition(pc, current);
+ else
+ playlist.SeekSongPosition(pc, current, seek_time);
+
+ if (state == PlayerState::PAUSE)
+ pc.Pause();
+ }
+
+ return true;
+}
+
+unsigned
+playlist_state_get_hash(const playlist &playlist,
+ PlayerControl &pc)
+{
+ const auto player_status = pc.GetStatus();
+
+ return playlist.queue.version ^
+ (player_status.state != PlayerState::STOP
+ ? (player_status.elapsed_time.ToS() << 8)
+ : 0) ^
+ (playlist.current >= 0
+ ? (playlist.queue.OrderToPosition(playlist.current) << 16)
+ : 0) ^
+ ((int)pc.GetCrossFade() << 20) ^
+ (unsigned(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/queue/PlaylistState.hxx b/src/queue/PlaylistState.hxx
new file mode 100644
index 000000000..3211b1178
--- /dev/null
+++ b/src/queue/PlaylistState.hxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Saving and loading the playlist to/from the state file.
+ *
+ */
+
+#ifndef MPD_PLAYLIST_STATE_HXX
+#define MPD_PLAYLIST_STATE_HXX
+
+struct playlist;
+struct PlayerControl;
+class TextFile;
+class BufferedOutputStream;
+class SongLoader;
+
+void
+playlist_state_save(BufferedOutputStream &os, const playlist &playlist,
+ PlayerControl &pc);
+
+bool
+playlist_state_restore(const char *line, TextFile &file,
+ const SongLoader &song_loader,
+ playlist &playlist, PlayerControl &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 playlist &playlist,
+ PlayerControl &c);
+
+#endif
diff --git a/src/queue/PlaylistTag.cxx b/src/queue/PlaylistTag.cxx
new file mode 100644
index 000000000..556e7f4e9
--- /dev/null
+++ b/src/queue/PlaylistTag.cxx
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Functions for editing the playlist (adding, removing, reordering
+ * songs in the queue).
+ *
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "PlaylistError.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
+#include "util/Error.hxx"
+
+bool
+playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value,
+ Error &error)
+{
+ const int position = queue.IdToPosition(id);
+ if (position < 0) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No such song");
+ return false;
+ }
+
+ DetachedSong &song = queue.Get(position);
+ if (song.IsFile()) {
+ error.Set(playlist_domain, int(PlaylistResult::DENIED),
+ "Cannot edit tags of local file");
+ return false;
+ }
+
+ {
+ TagBuilder tag(std::move(song.WritableTag()));
+ tag.AddItem(tag_type, value);
+ song.SetTag(tag.Commit());
+ }
+
+ queue.ModifyAtPosition(position);
+ OnModified();
+ return true;
+}
+
+bool
+playlist::ClearSongIdTag(unsigned id, TagType tag_type,
+ Error &error)
+{
+ const int position = queue.IdToPosition(id);
+ if (position < 0) {
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
+ "No such song");
+ return false;
+ }
+
+ DetachedSong &song = queue.Get(position);
+ if (song.IsFile()) {
+ error.Set(playlist_domain, int(PlaylistResult::DENIED),
+ "Cannot edit tags of local file");
+ return false;
+ }
+
+ {
+ TagBuilder tag(std::move(song.WritableTag()));
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES)
+ tag.RemoveAll();
+ else
+ tag.RemoveType(tag_type);
+ song.SetTag(tag.Commit());
+ }
+
+ queue.ModifyAtPosition(position);
+ OnModified();
+ return true;
+}
diff --git a/src/queue/PlaylistUpdate.cxx b/src/queue/PlaylistUpdate.cxx
new file mode 100644
index 000000000..8876711ef
--- /dev/null
+++ b/src/queue/PlaylistUpdate.cxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Playlist.hxx"
+#include "db/Interface.hxx"
+#include "db/LightSong.hxx"
+#include "DetachedSong.hxx"
+#include "tag/Tag.hxx"
+#include "Idle.hxx"
+#include "util/Error.hxx"
+
+static bool
+UpdatePlaylistSong(const Database &db, DetachedSong &song)
+{
+ if (!song.IsInDatabase() || !song.IsFile())
+ /* only update Songs instances that are "detached"
+ from the Database */
+ return false;
+
+ const LightSong *original = db.GetSong(song.GetURI(), IgnoreError());
+ if (original == nullptr)
+ /* not found - shouldn't happen, because the update
+ thread should ensure that all stale Song instances
+ have been purged */
+ return false;
+
+ if (original->mtime == song.GetLastModified()) {
+ /* not modified */
+ db.ReturnSong(original);
+ return false;
+ }
+
+ song.SetLastModified(original->mtime);
+ song.SetTag(*original->tag);
+
+ db.ReturnSong(original);
+ return true;
+}
+
+void
+playlist::DatabaseModified(const Database &db)
+{
+ bool modified = false;
+
+ for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) {
+ if (UpdatePlaylistSong(db, queue.Get(i))) {
+ queue.ModifyAtPosition(i);
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ queue.IncrementVersion();
+ idle_add(IDLE_PLAYLIST);
+ }
+}
diff --git a/src/queue/Queue.cxx b/src/queue/Queue.cxx
new file mode 100644
index 000000000..99b545ab1
--- /dev/null
+++ b/src/queue/Queue.cxx
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Queue.hxx"
+#include "DetachedSong.hxx"
+
+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];
+ ModifyAtPosition(position);
+}
+
+unsigned
+Queue::Append(DetachedSong &&song, uint8_t priority)
+{
+ assert(!IsFull());
+
+ const unsigned position = length++;
+ const unsigned id = id_table.Insert(position);
+
+ auto &item = items[position];
+ item.song = new DetachedSong(std::move(song));
+ 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);
+
+ delete items[position].song;
+
+ 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];
+
+ delete item->song;
+
+ id_table.Erase(item->id);
+ }
+
+ length = 0;
+}
+
+static void
+queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end)
+{
+ assert(queue != nullptr);
+ 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/Queue.hxx b/src/queue/Queue.hxx
new file mode 100644
index 000000000..016619e65
--- /dev/null
+++ b/src/queue/Queue.hxx
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_QUEUE_HXX
+#define MPD_QUEUE_HXX
+
+#include "Compiler.h"
+#include "IdTable.hxx"
+#include "util/LazyRandomEngine.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <stdint.h>
+
+class DetachedSong;
+
+/**
+ * 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 {
+ DetachedSong *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;
+
+ explicit Queue(unsigned max_length);
+
+ /**
+ * Deinitializes a queue object. It does not free the queue
+ * pointer itself.
+ */
+ ~Queue();
+
+ Queue(const Queue &) = delete;
+ Queue &operator=(const Queue &) = 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.
+ */
+ DetachedSong &Get(unsigned position) const {
+ assert(position < length);
+
+ return *items[position].song;
+ }
+
+ /**
+ * Returns the song at the specified order number.
+ */
+ DetachedSong &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". Call
+ * IncrementVersion() after all modifications have been made.
+ * number.
+ */
+ void ModifyAtPosition(unsigned position) {
+ assert(position < length);
+
+ items[position].version = version;
+ }
+
+ /**
+ * Marks the specified song as "modified". Call
+ * IncrementVersion() after all modifications have been made.
+ * number.
+ */
+ void ModifyAtOrder(unsigned order);
+
+ /**
+ * 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(DetachedSong &&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/queue/QueuePrint.cxx b/src/queue/QueuePrint.cxx
new file mode 100644
index 000000000..831ecafb9
--- /dev/null
+++ b/src/queue/QueuePrint.cxx
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "QueuePrint.hxx"
+#include "Queue.hxx"
+#include "SongFilter.hxx"
+#include "SongPrint.hxx"
+#include "client/Client.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 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 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 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 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 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 Queue &queue,
+ const SongFilter &filter)
+{
+ for (unsigned i = 0; i < queue.GetLength(); i++) {
+ const DetachedSong &song = queue.Get(i);
+
+ if (filter.Match(song))
+ queue_print_song_info(client, queue, i);
+ }
+}
diff --git a/src/queue/QueuePrint.hxx b/src/queue/QueuePrint.hxx
new file mode 100644
index 000000000..1aa876219
--- /dev/null
+++ b/src/queue/QueuePrint.hxx
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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 Queue &queue,
+ unsigned start, unsigned end);
+
+void
+queue_print_uris(Client &client, const Queue &queue,
+ unsigned start, unsigned end);
+
+void
+queue_print_changes_info(Client &client, const Queue &queue,
+ uint32_t version);
+
+void
+queue_print_changes_position(Client &client, const Queue &queue,
+ uint32_t version);
+
+void
+queue_find(Client &client, const Queue &queue,
+ const SongFilter &filter);
+
+#endif
diff --git a/src/queue/QueueSave.cxx b/src/queue/QueueSave.cxx
new file mode 100644
index 000000000..bc2702572
--- /dev/null
+++ b/src/queue/QueueSave.cxx
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "QueueSave.hxx"
+#include "Queue.hxx"
+#include "PlaylistError.hxx"
+#include "DetachedSong.hxx"
+#include "SongSave.hxx"
+#include "SongLoader.hxx"
+#include "playlist/PlaylistSong.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "fs/Traits.hxx"
+#include "Log.hxx"
+
+#include <stdlib.h>
+
+#define PRIO_LABEL "Prio: "
+
+static void
+queue_save_database_song(BufferedOutputStream &os,
+ int idx, const DetachedSong &song)
+{
+ os.Format("%i:%s\n", idx, song.GetURI());
+}
+
+static void
+queue_save_full_song(BufferedOutputStream &os, const DetachedSong &song)
+{
+ song_save(os, song);
+}
+
+static void
+queue_save_song(BufferedOutputStream &os, int idx, const DetachedSong &song)
+{
+ if (song.IsInDatabase() &&
+ song.GetStartTime().IsZero() && song.GetEndTime().IsZero())
+ /* use the brief format (just the URI) for "full"
+ database songs */
+ queue_save_database_song(os, idx, song);
+ else
+ /* use the long format (URI, range, tags) for the
+ rest, so all metadata survives a MPD restart */
+ queue_save_full_song(os, song);
+}
+
+void
+queue_save(BufferedOutputStream &os, const Queue &queue)
+{
+ for (unsigned i = 0; i < queue.GetLength(); i++) {
+ uint8_t prio = queue.GetPriorityAtPosition(i);
+ if (prio != 0)
+ os.Format(PRIO_LABEL "%u\n", prio);
+
+ queue_save_song(os, i, queue.Get(i));
+ }
+}
+
+void
+queue_load_song(TextFile &file, const SongLoader &loader,
+ const char *line, Queue &queue)
+{
+ if (queue.IsFull())
+ return;
+
+ uint8_t priority = 0;
+ if (StringStartsWith(line, PRIO_LABEL)) {
+ priority = strtoul(line + sizeof(PRIO_LABEL) - 1, nullptr, 10);
+
+ line = file.ReadLine();
+ if (line == nullptr)
+ return;
+ }
+
+ DetachedSong *song;
+
+ if (StringStartsWith(line, SONG_BEGIN)) {
+ const char *uri = line + sizeof(SONG_BEGIN) - 1;
+
+ Error error;
+ song = song_load(file, uri, error);
+ if (song == nullptr) {
+ LogError(error);
+ return;
+ }
+ } else {
+ char *endptr;
+ long ret = strtol(line, &endptr, 10);
+ if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
+ LogError(playlist_domain,
+ "Malformed playlist line in state file");
+ return;
+ }
+
+ const char *uri = endptr + 1;
+
+ song = new DetachedSong(uri);
+ }
+
+ if (!playlist_check_translate_song(*song, nullptr, loader)) {
+ delete song;
+ return;
+ }
+
+ queue.Append(std::move(*song), priority);
+ delete song;
+}
diff --git a/src/queue/QueueSave.hxx b/src/queue/QueueSave.hxx
new file mode 100644
index 000000000..3fb4dc1a6
--- /dev/null
+++ b/src/queue/QueueSave.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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
+
+struct Queue;
+class BufferedOutputStream;
+class TextFile;
+class SongLoader;
+
+void
+queue_save(BufferedOutputStream &os, const Queue &queue);
+
+/**
+ * Loads one song from the state file and appends it to the queue.
+ */
+void
+queue_load_song(TextFile &file, const SongLoader &loader,
+ const char *line, Queue &queue);
+
+#endif
diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx
new file mode 100644
index 000000000..b6f46f167
--- /dev/null
+++ b/src/sticker/SongSticker.cxx
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SongSticker.hxx"
+#include "StickerDatabase.hxx"
+#include "db/LightSong.hxx"
+#include "db/Interface.hxx"
+#include "util/Error.hxx"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+std::string
+sticker_song_get_value(const LightSong &song, const char *name)
+{
+ const auto uri = song.GetURI();
+ return sticker_load_value("song", uri.c_str(), name);
+}
+
+bool
+sticker_song_set_value(const LightSong &song,
+ const char *name, const char *value)
+{
+ const auto uri = song.GetURI();
+ return sticker_store_value("song", uri.c_str(), name, value);
+}
+
+bool
+sticker_song_delete(const LightSong &song)
+{
+ const auto uri = song.GetURI();
+ return sticker_delete("song", uri.c_str());
+}
+
+bool
+sticker_song_delete_value(const LightSong &song, const char *name)
+{
+ const auto uri = song.GetURI();
+ return sticker_delete_value("song", uri.c_str(), name);
+}
+
+struct sticker *
+sticker_song_get(const LightSong &song)
+{
+ const auto uri = song.GetURI();
+ return sticker_load("song", uri.c_str());
+}
+
+struct sticker_song_find_data {
+ const Database *db;
+ const char *base_uri;
+ size_t base_uri_length;
+
+ void (*func)(const LightSong &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;
+
+ const Database *db = data->db;
+ const LightSong *song = db->GetSong(uri, IgnoreError());
+ if (song != nullptr) {
+ data->func(*song, value, data->user_data);
+ db->ReturnSong(song);
+ }
+}
+
+bool
+sticker_song_find(const Database &db, const char *base_uri, const char *name,
+ void (*func)(const LightSong &song, const char *value,
+ void *user_data),
+ void *user_data)
+{
+ struct sticker_song_find_data data;
+ data.db = &db;
+ data.func = func;
+ data.user_data = user_data;
+
+ char *allocated;
+ data.base_uri = base_uri;
+ if (*data.base_uri != 0)
+ /* append slash to base_uri */
+ data.base_uri = allocated =
+ g_strconcat(data.base_uri, "/", nullptr);
+ else
+ /* searching in root directory - no trailing slash */
+ allocated = nullptr;
+
+ data.base_uri_length = strlen(data.base_uri);
+
+ bool success = sticker_find("song", data.base_uri, name,
+ sticker_song_find_cb, &data);
+ g_free(allocated);
+
+ return success;
+}
diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx
new file mode 100644
index 000000000..5956cd6f9
--- /dev/null
+++ b/src/sticker/SongSticker.hxx
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SONG_STICKER_HXX
+#define MPD_SONG_STICKER_HXX
+
+#include "Compiler.h"
+
+#include <string>
+
+struct LightSong;
+struct sticker;
+class Database;
+
+/**
+ * Returns one value from a song's sticker record. The caller must
+ * free the return value with g_free().
+ */
+gcc_pure
+std::string
+sticker_song_get_value(const LightSong &song, const char *name);
+
+/**
+ * Sets a sticker value in the specified song. Overwrites existing
+ * values.
+ */
+bool
+sticker_song_set_value(const LightSong &song,
+ const char *name, const char *value);
+
+/**
+ * Deletes a sticker from the database. All values are deleted.
+ */
+bool
+sticker_song_delete(const LightSong &song);
+
+/**
+ * Deletes a sticker value. Does nothing if the sticker did not
+ * exist.
+ */
+bool
+sticker_song_delete_value(const LightSong &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
+ */
+sticker *
+sticker_song_get(const LightSong &song);
+
+/**
+ * Finds stickers with the specified name below the specified
+ * directory.
+ *
+ * Caller must lock the #db_mutex.
+ *
+ * @param base_uri 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(const Database &db, const char *base_uri, const char *name,
+ void (*func)(const LightSong &song, const char *value,
+ void *user_data),
+ void *user_data);
+
+#endif
diff --git a/src/sticker/StickerDatabase.cxx b/src/sticker/StickerDatabase.cxx
new file mode 100644
index 000000000..93eaa900d
--- /dev/null
+++ b/src/sticker/StickerDatabase.cxx
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "StickerDatabase.hxx"
+#include "fs/Path.hxx"
+#include "Idle.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "util/Macros.hxx"
+#include "Log.hxx"
+
+#include <string>
+#include <map>
+
+#include <sqlite3.h>
+#include <assert.h>
+
+#if SQLITE_VERSION_NUMBER < 3003009
+#define sqlite3_prepare_v2 sqlite3_prepare
+#endif
+
+struct sticker {
+ std::map<std::string, std::string> table;
+};
+
+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[ARRAY_SIZE(sticker_sql)];
+
+static constexpr Domain sticker_domain("sticker");
+
+static void
+LogError(sqlite3 *db, const char *msg)
+{
+ FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db));
+}
+
+static sqlite3_stmt *
+sticker_prepare(const char *sql, Error &error)
+{
+ int ret;
+ sqlite3_stmt *stmt;
+
+ ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr);
+ if (ret != SQLITE_OK) {
+ error.Format(sticker_domain, ret,
+ "sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return nullptr;
+ }
+
+ return stmt;
+}
+
+bool
+sticker_global_init(Path path, Error &error)
+{
+ assert(!path.IsNull());
+
+ int ret;
+
+ /* open/create the sqlite database */
+
+ ret = sqlite3_open(path.c_str(), &sticker_db);
+ if (ret != SQLITE_OK) {
+ const std::string utf8 = path.ToUTF8();
+ error.Format(sticker_domain, ret,
+ "Failed to open sqlite database '%s': %s",
+ utf8.c_str(), sqlite3_errmsg(sticker_db));
+ return false;
+ }
+
+ /* create the table and index */
+
+ ret = sqlite3_exec(sticker_db, sticker_sql_create,
+ nullptr, nullptr, nullptr);
+ if (ret != SQLITE_OK) {
+ error.Format(sticker_domain, 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 < ARRAY_SIZE(sticker_sql); ++i) {
+ assert(sticker_sql[i] != nullptr);
+
+ sticker_stmt[i] = sticker_prepare(sticker_sql[i], error);
+ if (sticker_stmt[i] == nullptr)
+ return false;
+ }
+
+ return true;
+}
+
+void
+sticker_global_finish(void)
+{
+ if (sticker_db == nullptr)
+ /* not configured */
+ return;
+
+ for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) {
+ assert(sticker_stmt[i] != nullptr);
+
+ sqlite3_finalize(sticker_stmt[i]);
+ }
+
+ sqlite3_close(sticker_db);
+}
+
+bool
+sticker_enabled(void)
+{
+ return sticker_db != nullptr;
+}
+
+std::string
+sticker_load_value(const char *type, const char *uri, const char *name)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET];
+ int ret;
+
+ assert(sticker_enabled());
+ assert(type != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+
+ if (*name == 0)
+ return std::string();
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return std::string();
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return std::string();
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return std::string();
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ std::string value;
+ if (ret == SQLITE_ROW) {
+ /* record found */
+ value = (const char*)sqlite3_column_text(stmt, 0);
+ } else if (ret == SQLITE_DONE) {
+ /* no record found */
+ } else {
+ /* error */
+ LogError(sticker_db, "sqlite3_step() failed");
+ }
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ 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 != nullptr);
+ assert(uri != nullptr);
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ switch (ret) {
+ const char *name, *value;
+
+ case SQLITE_ROW:
+ name = (const char*)sqlite3_column_text(stmt, 0);
+ value = (const char*)sqlite3_column_text(stmt, 1);
+
+ table.insert(std::make_pair(name, value));
+ break;
+ case SQLITE_DONE:
+ break;
+ case SQLITE_BUSY:
+ /* no op */
+ break;
+ default:
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+ } while (ret != SQLITE_DONE);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return true;
+}
+
+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 != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+ assert(*name != 0);
+ assert(value != nullptr);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ 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 != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+ assert(*name != 0);
+ assert(value != nullptr);
+
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ 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 != nullptr);
+ assert(uri != nullptr);
+ assert(name != nullptr);
+ assert(value != nullptr);
+
+ 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 != nullptr);
+ assert(uri != nullptr);
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ 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 != nullptr);
+ assert(uri != nullptr);
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ } while (ret == SQLITE_BUSY);
+
+ if (ret != SQLITE_DONE) {
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+
+ ret = sqlite3_changes(sticker_db);
+
+ 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 sticker &sticker,
+ void (*func)(const char *name, const char *value,
+ void *user_data),
+ void *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 nullptr;
+
+ if (s.table.empty())
+ /* don't return empty sticker objects */
+ return nullptr;
+
+ return new sticker(std::move(s));
+}
+
+bool
+sticker_find(const char *type, const char *base_uri, const char *name,
+ void (*func)(const char *uri, const char *value,
+ void *user_data),
+ void *user_data)
+{
+ sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND];
+ int ret;
+
+ assert(type != nullptr);
+ assert(name != nullptr);
+ assert(func != nullptr);
+ assert(sticker_enabled());
+
+ sqlite3_reset(stmt);
+
+ ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ if (base_uri == nullptr)
+ base_uri = "";
+
+ ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr);
+ if (ret != SQLITE_OK) {
+ LogError(sticker_db, "sqlite3_bind_text() failed");
+ return false;
+ }
+
+ do {
+ ret = sqlite3_step(stmt);
+ 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:
+ LogError(sticker_db, "sqlite3_step() failed");
+ return false;
+ }
+ } while (ret != SQLITE_DONE);
+
+ sqlite3_reset(stmt);
+ sqlite3_clear_bindings(stmt);
+
+ return true;
+}
diff --git a/src/sticker/StickerDatabase.hxx b/src/sticker/StickerDatabase.hxx
new file mode 100644
index 000000000..8993489c4
--- /dev/null
+++ b/src/sticker/StickerDatabase.hxx
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * 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 "Compiler.h"
+
+#include <string>
+
+class Error;
+class Path;
+struct sticker;
+
+/**
+ * Opens the sticker database.
+ *
+ * @return true on success, false on error
+ */
+bool
+sticker_global_init(Path path, Error &error);
+
+/**
+ * Close the sticker database.
+ */
+void
+sticker_global_finish(void);
+
+/**
+ * Returns true if the sticker database is configured and available.
+ */
+gcc_const
+bool
+sticker_enabled(void);
+
+/**
+ * Returns one value from an object's sticker record. Returns an
+ * empty string if the value doesn't exist.
+ */
+std::string
+sticker_load_value(const char *type, const char *uri, const char *name);
+
+/**
+ * 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(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 nullptr if none was found
+ */
+gcc_pure
+const char *
+sticker_get_value(const 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 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 nullptr on error or if there is no sticker
+ */
+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 nullptr 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/sticker/StickerPrint.cxx b/src/sticker/StickerPrint.cxx
new file mode 100644
index 000000000..a952ff203
--- /dev/null
+++ b/src/sticker/StickerPrint.cxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "StickerPrint.hxx"
+#include "StickerDatabase.hxx"
+#include "client/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 sticker &sticker)
+{
+ sticker_foreach(sticker, print_sticker_cb, &client);
+}
diff --git a/src/sticker/StickerPrint.hxx b/src/sticker/StickerPrint.hxx
new file mode 100644
index 000000000..39f3dc09e
--- /dev/null
+++ b/src/sticker/StickerPrint.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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 sticker &sticker);
+
+#endif
diff --git a/src/storage/CompositeStorage.cxx b/src/storage/CompositeStorage.cxx
new file mode 100644
index 000000000..89a2fc756
--- /dev/null
+++ b/src/storage/CompositeStorage.cxx
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CompositeStorage.hxx"
+#include "FileInfo.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <set>
+
+#include <string.h>
+
+static constexpr Domain composite_domain("composite");
+
+/**
+ * Combines the directory entries of another #StorageDirectoryReader
+ * instance and the virtual directory entries.
+ */
+class CompositeDirectoryReader final : public StorageDirectoryReader {
+ StorageDirectoryReader *other;
+
+ std::set<std::string> names;
+ std::set<std::string>::const_iterator current, next;
+
+public:
+ template<typename M>
+ CompositeDirectoryReader(StorageDirectoryReader *_other,
+ const M &map)
+ :other(_other) {
+ for (const auto &i : map)
+ names.insert(i.first);
+ next = names.begin();
+ }
+
+ virtual ~CompositeDirectoryReader() {
+ delete other;
+ }
+
+ /* virtual methods from class StorageDirectoryReader */
+ const char *Read() override;
+ bool GetInfo(bool follow, FileInfo &info, Error &error) override;
+};
+
+const char *
+CompositeDirectoryReader::Read()
+{
+ if (other != nullptr) {
+ const char *name = other->Read();
+ if (name != nullptr) {
+ names.erase(name);
+ return name;
+ }
+
+ delete other;
+ other = nullptr;
+ }
+
+ if (next == names.end())
+ return nullptr;
+
+ current = next++;
+ return current->c_str();
+}
+
+bool
+CompositeDirectoryReader::GetInfo(bool follow, FileInfo &info,
+ Error &error)
+{
+ if (other != nullptr)
+ return other->GetInfo(follow, info, error);
+
+ assert(current != names.end());
+
+ info.type = FileInfo::Type::DIRECTORY;
+ info.mtime = 0;
+ info.device = 0;
+ info.inode = 0;
+ return true;
+}
+
+static std::string
+NextSegment(const char *&uri_r)
+{
+ const char *uri = uri_r;
+ const char *slash = strchr(uri, '/');
+ if (slash == nullptr) {
+ uri_r += strlen(uri);
+ return std::string(uri);
+ } else {
+ uri_r = slash + 1;
+ return std::string(uri, slash);
+ }
+}
+
+CompositeStorage::Directory::~Directory()
+{
+ delete storage;
+}
+
+const CompositeStorage::Directory *
+CompositeStorage::Directory::Find(const char *uri) const
+{
+ const Directory *directory = this;
+ while (*uri != 0) {
+ const std::string name = NextSegment(uri);
+ auto i = directory->children.find(name);
+ if (i == directory->children.end())
+ return nullptr;
+
+ directory = &i->second;
+ }
+
+ return directory;
+}
+
+CompositeStorage::Directory &
+CompositeStorage::Directory::Make(const char *uri)
+{
+ Directory *directory = this;
+ while (*uri != 0) {
+ const std::string name = NextSegment(uri);
+#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+ auto i = directory->children.emplace(std::move(name),
+ Directory());
+#else
+ auto i = directory->children.insert(std::make_pair(std::move(name),
+ Directory()));
+#endif
+ directory = &i.first->second;
+ }
+
+ return *directory;
+}
+
+bool
+CompositeStorage::Directory::Unmount()
+{
+ if (storage == nullptr)
+ return false;
+
+ delete storage;
+ storage = nullptr;
+ return true;
+}
+
+bool
+CompositeStorage::Directory::Unmount(const char *uri)
+{
+ if (*uri == 0)
+ return Unmount();
+
+ const std::string name = NextSegment(uri);
+
+ auto i = children.find(name);
+ if (i == children.end() || !i->second.Unmount(uri))
+ return false;
+
+ if (i->second.IsEmpty())
+ children.erase(i);
+
+ return true;
+
+}
+
+bool
+CompositeStorage::Directory::MapToRelativeUTF8(std::string &buffer,
+ const char *uri) const
+{
+ if (storage != nullptr) {
+ const char *result = storage->MapToRelativeUTF8(uri);
+ if (result != nullptr) {
+ buffer = result;
+ return true;
+ }
+ }
+
+ for (const auto &i : children) {
+ if (i.second.MapToRelativeUTF8(buffer, uri)) {
+ buffer.insert(buffer.begin(), '/');
+ buffer.insert(buffer.begin(),
+ i.first.begin(), i.first.end());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+CompositeStorage::CompositeStorage()
+{
+}
+
+CompositeStorage::~CompositeStorage()
+{
+}
+
+Storage *
+CompositeStorage::GetMount(const char *uri)
+{
+ const ScopeLock protect(mutex);
+
+ auto result = FindStorage(uri);
+ if (*result.uri != 0)
+ /* not a mount point */
+ return nullptr;
+
+ return result.directory->storage;
+}
+
+void
+CompositeStorage::Mount(const char *uri, Storage *storage)
+{
+ const ScopeLock protect(mutex);
+
+ Directory &directory = root.Make(uri);
+ if (directory.storage != nullptr)
+ delete directory.storage;
+ directory.storage = storage;
+}
+
+bool
+CompositeStorage::Unmount(const char *uri)
+{
+ const ScopeLock protect(mutex);
+
+ return root.Unmount(uri);
+}
+
+CompositeStorage::FindResult
+CompositeStorage::FindStorage(const char *uri) const
+{
+ FindResult result{&root, uri};
+
+ const Directory *directory = &root;
+ while (*uri != 0) {
+ const std::string name = NextSegment(uri);
+
+ auto i = directory->children.find(name);
+ if (i == directory->children.end())
+ break;
+
+ directory = &i->second;
+ if (directory->storage != nullptr)
+ result = FindResult{directory, uri};
+ }
+
+ return result;
+}
+
+CompositeStorage::FindResult
+CompositeStorage::FindStorage(const char *uri, Error &error) const
+{
+ auto result = FindStorage(uri);
+ if (result.directory == nullptr)
+ error.Set(composite_domain, "No such directory");
+ return result;
+}
+
+bool
+CompositeStorage::GetInfo(const char *uri, bool follow, FileInfo &info,
+ Error &error)
+{
+ const ScopeLock protect(mutex);
+
+ auto f = FindStorage(uri, error);
+ if (f.directory->storage != nullptr &&
+ f.directory->storage->GetInfo(f.uri, follow, info, error))
+ return true;
+
+ const Directory *directory = f.directory->Find(f.uri);
+ if (directory != nullptr) {
+ error.Clear();
+ info.type = FileInfo::Type::DIRECTORY;
+ info.mtime = 0;
+ info.device = 0;
+ info.inode = 0;
+ return true;
+ }
+
+ return false;
+}
+
+StorageDirectoryReader *
+CompositeStorage::OpenDirectory(const char *uri,
+ Error &error)
+{
+ const ScopeLock protect(mutex);
+
+ auto f = FindStorage(uri, error);
+ const Directory *directory = f.directory->Find(f.uri);
+ if (directory == nullptr || directory->children.empty()) {
+ /* no virtual directories here */
+
+ if (f.directory->storage == nullptr)
+ return nullptr;
+
+ return f.directory->storage->OpenDirectory(f.uri, error);
+ }
+
+ StorageDirectoryReader *other =
+ f.directory->storage->OpenDirectory(f.uri, IgnoreError());
+ return new CompositeDirectoryReader(other, directory->children);
+}
+
+std::string
+CompositeStorage::MapUTF8(const char *uri) const
+{
+ const ScopeLock protect(mutex);
+
+ auto f = FindStorage(uri);
+ if (f.directory->storage == nullptr)
+ return std::string();
+
+ return f.directory->storage->MapUTF8(f.uri);
+}
+
+AllocatedPath
+CompositeStorage::MapFS(const char *uri) const
+{
+ const ScopeLock protect(mutex);
+
+ auto f = FindStorage(uri);
+ if (f.directory->storage == nullptr)
+ return AllocatedPath::Null();
+
+ return f.directory->storage->MapFS(f.uri);
+}
+
+const char *
+CompositeStorage::MapToRelativeUTF8(const char *uri) const
+{
+ const ScopeLock protect(mutex);
+
+ if (root.storage != nullptr) {
+ const char *result = root.storage->MapToRelativeUTF8(uri);
+ if (result != nullptr)
+ return result;
+ }
+
+ if (!root.MapToRelativeUTF8(relative_buffer, uri))
+ return nullptr;
+
+ return relative_buffer.c_str();
+}
diff --git a/src/storage/CompositeStorage.hxx b/src/storage/CompositeStorage.hxx
new file mode 100644
index 000000000..c3695c79d
--- /dev/null
+++ b/src/storage/CompositeStorage.hxx
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_COMPOSITE_STORAGE_HXX
+#define MPD_COMPOSITE_STORAGE_HXX
+
+#include "check.h"
+#include "StorageInterface.hxx"
+#include "thread/Mutex.hxx"
+#include "Compiler.h"
+
+#include <string>
+#include <map>
+
+class Error;
+class Storage;
+
+/**
+ * A #Storage implementation that combines multiple other #Storage
+ * instances in one virtual tree. It is used to "mount" new #Storage
+ * instances into the storage tree.
+ *
+ * This class is thread-safe: mounts may be added and removed at any
+ * time in any thread.
+ */
+class CompositeStorage final : public Storage {
+ /**
+ * A node in the virtual directory tree.
+ */
+ struct Directory {
+ /**
+ * The #Storage mounted n this virtual directory. All
+ * "leaf" Directory instances must have a #Storage.
+ * Other Directory instances may have one, and child
+ * mounts will be "mixed" in.
+ */
+ Storage *storage;
+
+ std::map<std::string, Directory> children;
+
+ Directory():storage(nullptr) {}
+ ~Directory();
+
+ gcc_pure
+ bool IsEmpty() const {
+ return storage == nullptr && children.empty();
+ }
+
+ gcc_pure
+ const Directory *Find(const char *uri) const;
+
+ Directory &Make(const char *uri);
+
+ bool Unmount();
+ bool Unmount(const char *uri);
+
+ gcc_pure
+ bool MapToRelativeUTF8(std::string &buffer,
+ const char *uri) const;
+ };
+
+ struct FindResult {
+ const Directory *directory;
+ const char *uri;
+ };
+
+ /**
+ * Protects the virtual #Directory tree.
+ *
+ * TODO: use readers-writer lock
+ */
+ mutable Mutex mutex;
+
+ Directory root;
+
+ mutable std::string relative_buffer;
+
+public:
+ CompositeStorage();
+ virtual ~CompositeStorage();
+
+ /**
+ * Get the #Storage at the specified mount point. Returns
+ * nullptr if the given URI is not a mount point.
+ *
+ * The returned pointer is unprotected. No other thread is
+ * allowed to unmount the given mount point while the return
+ * value is being used.
+ */
+ gcc_pure gcc_nonnull_all
+ Storage *GetMount(const char *uri);
+
+ /**
+ * Call the given function for each mounted storage, including
+ * the root storage. Passes mount point URI and the a const
+ * Storage reference to the function.
+ */
+ template<typename T>
+ void VisitMounts(T t) const {
+ const ScopeLock protect(mutex);
+ std::string uri;
+ VisitMounts(uri, root, t);
+ }
+
+ void Mount(const char *uri, Storage *storage);
+ bool Unmount(const char *uri);
+
+ /* virtual methods from class Storage */
+ bool GetInfo(const char *uri, bool follow, FileInfo &info,
+ Error &error) override;
+
+ StorageDirectoryReader *OpenDirectory(const char *uri,
+ Error &error) override;
+
+ std::string MapUTF8(const char *uri) const override;
+
+ AllocatedPath MapFS(const char *uri) const override;
+
+ const char *MapToRelativeUTF8(const char *uri) const override;
+
+private:
+ template<typename T>
+ void VisitMounts(std::string &uri, const Directory &directory,
+ T t) const {
+ const Storage *const storage = directory.storage;
+ if (storage != nullptr)
+ t(uri.c_str(), *storage);
+
+ if (!uri.empty())
+ uri.push_back('/');
+
+ const size_t uri_length = uri.length();
+
+ for (const auto &i : directory.children) {
+ uri.resize(uri_length);
+ uri.append(i.first);
+
+ VisitMounts(uri, i.second, t);
+ }
+ }
+
+ gcc_pure
+ FindResult FindStorage(const char *uri) const;
+ FindResult FindStorage(const char *uri, Error &error) const;
+
+ const char *MapToRelativeUTF8(const Directory &directory,
+ const char *uri) const;
+};
+
+#endif
diff --git a/src/storage/Configured.cxx b/src/storage/Configured.cxx
new file mode 100644
index 000000000..41541673b
--- /dev/null
+++ b/src/storage/Configured.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Configured.hxx"
+#include "Registry.hxx"
+#include "plugins/LocalStorage.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigError.hxx"
+#include "fs/StandardDirectory.hxx"
+#include "fs/CheckFile.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+static Storage *
+CreateConfiguredStorageUri(EventLoop &event_loop, const char *uri,
+ Error &error)
+{
+ Storage *storage = CreateStorageURI(event_loop, uri, error);
+ if (storage == nullptr && !error.IsDefined())
+ error.Format(config_domain,
+ "Unrecognized storage URI: %s", uri);
+ return storage;
+}
+
+static AllocatedPath
+GetConfiguredMusicDirectory(Error &error)
+{
+ AllocatedPath path = config_get_path(CONF_MUSIC_DIR, error);
+ if (path.IsNull() && !error.IsDefined())
+ path = GetUserMusicDir();
+
+ return path;
+}
+
+static Storage *
+CreateConfiguredStorageLocal(Error &error)
+{
+ AllocatedPath path = GetConfiguredMusicDirectory(error);
+ if (path.IsNull())
+ return nullptr;
+
+ path.ChopSeparators();
+ CheckDirectoryReadable(path);
+ return CreateLocalStorage(path);
+}
+
+Storage *
+CreateConfiguredStorage(EventLoop &event_loop, Error &error)
+{
+ assert(!error.IsDefined());
+
+ auto uri = config_get_string(CONF_MUSIC_DIR, nullptr);
+ if (uri != nullptr && uri_has_scheme(uri))
+ return CreateConfiguredStorageUri(event_loop, uri, error);
+
+ return CreateConfiguredStorageLocal(error);
+}
+
+bool
+IsStorageConfigured()
+{
+ return config_get_string(CONF_MUSIC_DIR, nullptr) != nullptr;
+}
diff --git a/src/storage/Configured.hxx b/src/storage/Configured.hxx
new file mode 100644
index 000000000..828a192c3
--- /dev/null
+++ b/src/storage/Configured.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_CONFIG_HXX
+#define MPD_STORAGE_CONFIG_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+class Error;
+class Storage;
+class EventLoop;
+
+/**
+ * Read storage configuration settings and create a #Storage instance
+ * from it. Returns nullptr on error or if no storage is configured
+ * (no #Error set in that case).
+ */
+Storage *
+CreateConfiguredStorage(EventLoop &event_loop, Error &error);
+
+/**
+ * Returns true if there is configuration for a #Storage instance.
+ */
+gcc_const
+bool
+IsStorageConfigured();
+
+#endif
diff --git a/src/storage/FileInfo.hxx b/src/storage/FileInfo.hxx
new file mode 100644
index 000000000..8dd152c0a
--- /dev/null
+++ b/src/storage/FileInfo.hxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_FILE_INFO_HXX
+#define MPD_STORAGE_FILE_INFO_HXX
+
+#include "check.h"
+
+#include <time.h>
+#include <stdint.h>
+
+struct FileInfo {
+ enum class Type : uint8_t {
+ OTHER,
+ REGULAR,
+ DIRECTORY,
+ };
+
+ Type type;
+
+ /**
+ * The file size in bytes. Only valid for #Type::REGULAR.
+ */
+ uint64_t size;
+
+ /**
+ * The modification time. 0 means unknown / not applicable.
+ */
+ time_t mtime;
+
+ /**
+ * Device id and inode number. 0 means unknown / not
+ * applicable.
+ */
+ unsigned device, inode;
+
+ bool IsRegular() const {
+ return type == Type::REGULAR;
+ }
+
+ bool IsDirectory() const {
+ return type == Type::DIRECTORY;
+ }
+};
+
+#endif
diff --git a/src/storage/MemoryDirectoryReader.cxx b/src/storage/MemoryDirectoryReader.cxx
new file mode 100644
index 000000000..160836b1a
--- /dev/null
+++ b/src/storage/MemoryDirectoryReader.cxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MemoryDirectoryReader.hxx"
+
+#include <assert.h>
+
+const char *
+MemoryStorageDirectoryReader::Read()
+{
+ if (first)
+ first = false;
+ else
+ entries.pop_front();
+
+ if (entries.empty())
+ return nullptr;
+
+ return entries.front().name.c_str();
+}
+
+bool
+MemoryStorageDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info,
+ gcc_unused Error &error)
+{
+ assert(!first);
+ assert(!entries.empty());
+
+ info = entries.front().info;
+ return true;
+}
diff --git a/src/storage/MemoryDirectoryReader.hxx b/src/storage/MemoryDirectoryReader.hxx
new file mode 100644
index 000000000..1345082cb
--- /dev/null
+++ b/src/storage/MemoryDirectoryReader.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_MEMORY_DIRECTORY_READER_HXX
+#define MPD_STORAGE_MEMORY_DIRECTORY_READER_HXX
+
+#include "check.h"
+#include "StorageInterface.hxx"
+#include "FileInfo.hxx"
+
+#include <string>
+#include <forward_list>
+
+/**
+ * A #StorageDirectoryReader implementation that returns directory
+ * entries from a memory allocation.
+ */
+class MemoryStorageDirectoryReader final : public StorageDirectoryReader {
+public:
+ struct Entry {
+ std::string name;
+
+ FileInfo info;
+
+ template<typename N>
+ explicit Entry(N &&_name):name(std::forward<N>(_name)) {}
+ };
+
+ typedef std::forward_list<Entry> List;
+
+private:
+ List entries;
+
+ bool first;
+
+public:
+ MemoryStorageDirectoryReader()
+ :first(true) {}
+
+ MemoryStorageDirectoryReader(MemoryStorageDirectoryReader &&src)
+ :entries(std::move(src.entries)), first(src.first) {}
+
+ MemoryStorageDirectoryReader(List &&_entries)
+ :entries(std::move(_entries)), first(true) {}
+
+ /* virtual methods from class StorageDirectoryReader */
+ const char *Read() override;
+ bool GetInfo(bool follow, FileInfo &info, Error &error) override;
+};
+
+#endif
diff --git a/src/storage/Registry.cxx b/src/storage/Registry.cxx
new file mode 100644
index 000000000..d8e273fd5
--- /dev/null
+++ b/src/storage/Registry.cxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Registry.hxx"
+#include "StoragePlugin.hxx"
+#include "plugins/LocalStorage.hxx"
+#include "plugins/SmbclientStorage.hxx"
+#include "plugins/NfsStorage.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+const StoragePlugin *const storage_plugins[] = {
+ &local_storage_plugin,
+#ifdef ENABLE_SMBCLIENT
+ &smbclient_storage_plugin,
+#endif
+#ifdef ENABLE_NFS
+ &nfs_storage_plugin,
+#endif
+ nullptr
+};
+
+const StoragePlugin *
+GetStoragePluginByName(const char *name)
+{
+ for (auto i = storage_plugins; *i != nullptr; ++i) {
+ const StoragePlugin &plugin = **i;
+ if (strcmp(plugin.name, name) == 0)
+ return *i;
+ }
+
+ return nullptr;
+}
+
+Storage *
+CreateStorageURI(EventLoop &event_loop, const char *uri, Error &error)
+{
+ assert(!error.IsDefined());
+
+ for (auto i = storage_plugins; *i != nullptr; ++i) {
+ const StoragePlugin &plugin = **i;
+
+ if (plugin.create_uri == nullptr)
+ continue;
+
+ Storage *storage = plugin.create_uri(event_loop, uri, error);
+ if (storage != nullptr || error.IsDefined())
+ return storage;
+ }
+
+ return nullptr;
+}
diff --git a/src/storage/Registry.hxx b/src/storage/Registry.hxx
new file mode 100644
index 000000000..cb3a78f11
--- /dev/null
+++ b/src/storage/Registry.hxx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_REGISTRY_HXX
+#define MPD_STORAGE_REGISTRY_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+struct StoragePlugin;
+class Storage;
+class Error;
+class EventLoop;
+
+/**
+ * nullptr terminated list of all storage plugins which were enabled at
+ * compile time.
+ */
+extern const StoragePlugin *const storage_plugins[];
+
+gcc_nonnull_all gcc_pure
+const StoragePlugin *
+GetStoragePluginByName(const char *name);
+
+gcc_nonnull_all gcc_malloc
+Storage *
+CreateStorageURI(EventLoop &event_loop, const char *uri, Error &error);
+
+#endif
diff --git a/src/storage/StorageInterface.cxx b/src/storage/StorageInterface.cxx
new file mode 100644
index 000000000..93c50a8ac
--- /dev/null
+++ b/src/storage/StorageInterface.cxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "StorageInterface.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/Traits.hxx"
+
+AllocatedPath
+Storage::MapFS(gcc_unused const char *uri_utf8) const
+{
+ return AllocatedPath::Null();
+}
+
+AllocatedPath
+Storage::MapChildFS(const char *uri_utf8,
+ const char *child_utf8) const
+{
+ const auto uri2 = PathTraitsUTF8::Build(uri_utf8, child_utf8);
+ return MapFS(uri2.c_str());
+}
diff --git a/src/storage/StorageInterface.hxx b/src/storage/StorageInterface.hxx
new file mode 100644
index 000000000..4484815bc
--- /dev/null
+++ b/src/storage/StorageInterface.hxx
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_INTERFACE_HXX
+#define MPD_STORAGE_INTERFACE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <string>
+
+struct FileInfo;
+class AllocatedPath;
+class Error;
+
+class StorageDirectoryReader {
+public:
+ StorageDirectoryReader() = default;
+ StorageDirectoryReader(const StorageDirectoryReader &) = delete;
+ virtual ~StorageDirectoryReader() {}
+
+ virtual const char *Read() = 0;
+ virtual bool GetInfo(bool follow, FileInfo &info, Error &error) = 0;
+};
+
+class Storage {
+public:
+ Storage() = default;
+ Storage(const Storage &) = delete;
+ virtual ~Storage() {}
+
+ virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) = 0;
+
+ virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) = 0;
+
+ /**
+ * Map the given relative URI to an absolute URI.
+ */
+ gcc_pure
+ virtual std::string MapUTF8(const char *uri_utf8) const = 0;
+
+ /**
+ * Map the given relative URI to a local file path. Returns
+ * AllocatedPath::Null() on error or if this storage does not
+ * support local files.
+ */
+ gcc_pure
+ virtual AllocatedPath MapFS(const char *uri_utf8) const;
+
+ gcc_pure
+ AllocatedPath MapChildFS(const char *uri_utf8,
+ const char *child_utf8) const;
+
+ /**
+ * Check if the given URI points inside this storage. If yes,
+ * then it returns a relative URI (pointing inside the given
+ * string); if not, returns nullptr.
+ */
+ gcc_pure
+ virtual const char *MapToRelativeUTF8(const char *uri_utf8) const = 0;
+};
+
+#endif
diff --git a/src/storage/StoragePlugin.hxx b/src/storage/StoragePlugin.hxx
new file mode 100644
index 000000000..15f431105
--- /dev/null
+++ b/src/storage/StoragePlugin.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_PLUGIN_HXX
+#define MPD_STORAGE_PLUGIN_HXX
+
+#include "check.h"
+
+class Error;
+class Storage;
+class EventLoop;
+
+struct StoragePlugin {
+ const char *name;
+
+ Storage *(*create_uri)(EventLoop &event_loop, const char *uri,
+ Error &error);
+};
+
+#endif
diff --git a/src/storage/plugins/LocalStorage.cxx b/src/storage/plugins/LocalStorage.cxx
new file mode 100644
index 000000000..b965ceea8
--- /dev/null
+++ b/src/storage/plugins/LocalStorage.cxx
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LocalStorage.hxx"
+#include "storage/StoragePlugin.hxx"
+#include "storage/StorageInterface.hxx"
+#include "storage/FileInfo.hxx"
+#include "util/Error.hxx"
+#include "fs/FileSystem.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/DirectoryReader.hxx"
+
+#include <string>
+
+class LocalDirectoryReader final : public StorageDirectoryReader {
+ AllocatedPath base_fs;
+
+ DirectoryReader reader;
+
+ std::string name_utf8;
+
+public:
+ LocalDirectoryReader(AllocatedPath &&_base_fs)
+ :base_fs(std::move(_base_fs)), reader(base_fs) {}
+
+ bool HasFailed() {
+ return reader.HasFailed();
+ }
+
+ /* virtual methods from class StorageDirectoryReader */
+ const char *Read() override;
+ bool GetInfo(bool follow, FileInfo &info, Error &error) override;
+};
+
+class LocalStorage final : public Storage {
+ const AllocatedPath base_fs;
+ const std::string base_utf8;
+
+public:
+ explicit LocalStorage(Path _base_fs)
+ :base_fs(_base_fs), base_utf8(base_fs.ToUTF8()) {
+ assert(!base_fs.IsNull());
+ assert(!base_utf8.empty());
+ }
+
+ /* virtual methods from class Storage */
+ bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) override;
+
+ StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) override;
+
+ std::string MapUTF8(const char *uri_utf8) const override;
+
+ AllocatedPath MapFS(const char *uri_utf8) const override;
+
+ const char *MapToRelativeUTF8(const char *uri_utf8) const override;
+
+private:
+ AllocatedPath MapFS(const char *uri_utf8, Error &error) const;
+};
+
+static bool
+Stat(Path path, bool follow, FileInfo &info, Error &error)
+{
+ struct stat st;
+ if (!StatFile(path, st, follow)) {
+ error.SetErrno();
+
+ const auto path_utf8 = path.ToUTF8();
+ error.FormatPrefix("Failed to stat %s: ", path_utf8.c_str());
+ return false;
+ }
+
+ if (S_ISREG(st.st_mode))
+ info.type = FileInfo::Type::REGULAR;
+ else if (S_ISDIR(st.st_mode))
+ info.type = FileInfo::Type::DIRECTORY;
+ else
+ info.type = FileInfo::Type::OTHER;
+
+ info.size = st.st_size;
+ info.mtime = st.st_mtime;
+ info.device = st.st_dev;
+ info.inode = st.st_ino;
+ return true;
+}
+
+std::string
+LocalStorage::MapUTF8(const char *uri_utf8) const
+{
+ assert(uri_utf8 != nullptr);
+
+ if (*uri_utf8 == 0)
+ return base_utf8;
+
+ return PathTraitsUTF8::Build(base_utf8.c_str(), uri_utf8);
+}
+
+AllocatedPath
+LocalStorage::MapFS(const char *uri_utf8, Error &error) const
+{
+ assert(uri_utf8 != nullptr);
+
+ if (*uri_utf8 == 0)
+ return base_fs;
+
+ AllocatedPath path_fs = AllocatedPath::FromUTF8(uri_utf8, error);
+ if (!path_fs.IsNull())
+ path_fs = AllocatedPath::Build(base_fs, path_fs);
+
+ return path_fs;
+}
+
+AllocatedPath
+LocalStorage::MapFS(const char *uri_utf8) const
+{
+ return MapFS(uri_utf8, IgnoreError());
+}
+
+const char *
+LocalStorage::MapToRelativeUTF8(const char *uri_utf8) const
+{
+ return PathTraitsUTF8::Relative(base_utf8.c_str(), uri_utf8);
+}
+
+bool
+LocalStorage::GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error)
+{
+ AllocatedPath path_fs = MapFS(uri_utf8, error);
+ if (path_fs.IsNull())
+ return false;
+
+ return Stat(path_fs, follow, info, error);
+}
+
+StorageDirectoryReader *
+LocalStorage::OpenDirectory(const char *uri_utf8, Error &error)
+{
+ AllocatedPath path_fs = MapFS(uri_utf8, error);
+ if (path_fs.IsNull())
+ return nullptr;
+
+ LocalDirectoryReader *reader =
+ new LocalDirectoryReader(std::move(path_fs));
+ if (reader->HasFailed()) {
+ error.FormatErrno("Failed to open '%s'", uri_utf8);
+ delete reader;
+ return nullptr;
+ }
+
+ return reader;
+}
+
+gcc_pure
+static bool
+SkipNameFS(const char *name_fs)
+{
+ return name_fs[0] == '.' &&
+ (name_fs[1] == 0 ||
+ (name_fs[1] == '.' && name_fs[2] == 0));
+}
+
+const char *
+LocalDirectoryReader::Read()
+{
+ while (reader.ReadEntry()) {
+ const Path name_fs = reader.GetEntry();
+ if (SkipNameFS(name_fs.c_str()))
+ continue;
+
+ name_utf8 = name_fs.ToUTF8();
+ if (name_utf8.empty())
+ continue;
+
+ return name_utf8.c_str();
+ }
+
+ return nullptr;
+}
+
+bool
+LocalDirectoryReader::GetInfo(bool follow, FileInfo &info, Error &error)
+{
+ const AllocatedPath path_fs =
+ AllocatedPath::Build(base_fs, reader.GetEntry());
+ return Stat(path_fs, follow, info, error);
+}
+
+Storage *
+CreateLocalStorage(Path base_fs)
+{
+ return new LocalStorage(base_fs);
+}
+
+const StoragePlugin local_storage_plugin = {
+ "local",
+ nullptr,
+};
diff --git a/src/storage/plugins/LocalStorage.hxx b/src/storage/plugins/LocalStorage.hxx
new file mode 100644
index 000000000..7295d38e7
--- /dev/null
+++ b/src/storage/plugins/LocalStorage.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_LOCAL_HXX
+#define MPD_STORAGE_LOCAL_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+struct StoragePlugin;
+class Storage;
+class Path;
+
+extern const StoragePlugin local_storage_plugin;
+
+gcc_malloc gcc_nonnull_all
+Storage *
+CreateLocalStorage(Path base_fs);
+
+#endif
diff --git a/src/storage/plugins/NfsStorage.cxx b/src/storage/plugins/NfsStorage.cxx
new file mode 100644
index 000000000..823d662c5
--- /dev/null
+++ b/src/storage/plugins/NfsStorage.cxx
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NfsStorage.hxx"
+#include "storage/StoragePlugin.hxx"
+#include "storage/StorageInterface.hxx"
+#include "storage/FileInfo.hxx"
+#include "storage/MemoryDirectoryReader.hxx"
+#include "lib/nfs/Blocking.hxx"
+#include "lib/nfs/Domain.hxx"
+#include "lib/nfs/Base.hxx"
+#include "lib/nfs/Lease.hxx"
+#include "lib/nfs/Connection.hxx"
+#include "lib/nfs/Glue.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "util/Error.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "event/Loop.hxx"
+#include "event/Call.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "event/TimeoutMonitor.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+#include <nfsc/libnfs-raw-nfs.h>
+}
+
+#include <string>
+
+#include <assert.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+class NfsStorage final
+ : public Storage, NfsLease, DeferredMonitor, TimeoutMonitor {
+
+ enum class State {
+ INITIAL, CONNECTING, READY, DELAY,
+ };
+
+ const std::string base;
+
+ const std::string server, export_name;
+
+ NfsConnection *connection;
+
+ Mutex mutex;
+ Cond cond;
+ State state;
+ Error last_error;
+
+public:
+ NfsStorage(EventLoop &_loop, const char *_base,
+ std::string &&_server, std::string &&_export_name)
+ :DeferredMonitor(_loop), TimeoutMonitor(_loop),
+ base(_base),
+ server(std::move(_server)),
+ export_name(std::move(_export_name)),
+ state(State::INITIAL) {
+ nfs_init();
+ }
+
+ ~NfsStorage() {
+ BlockingCall(GetEventLoop(), [this](){ Disconnect(); });
+ nfs_finish();
+ }
+
+ /* virtual methods from class Storage */
+ bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) override;
+
+ StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) override;
+
+ std::string MapUTF8(const char *uri_utf8) const override;
+
+ const char *MapToRelativeUTF8(const char *uri_utf8) const override;
+
+ /* virtual methods from NfsLease */
+ void OnNfsConnectionReady() final {
+ assert(state == State::CONNECTING);
+
+ SetState(State::READY);
+ }
+
+ void OnNfsConnectionFailed(gcc_unused const Error &error) final {
+ assert(state == State::CONNECTING);
+
+ SetState(State::DELAY, error);
+ TimeoutMonitor::ScheduleSeconds(60);
+ }
+
+ void OnNfsConnectionDisconnected(gcc_unused const Error &error) final {
+ assert(state == State::READY);
+
+ SetState(State::DELAY, error);
+ TimeoutMonitor::ScheduleSeconds(5);
+ }
+
+ /* virtual methods from DeferredMonitor */
+ void RunDeferred() final {
+ if (state == State::INITIAL)
+ Connect();
+ }
+
+ /* virtual methods from TimeoutMonitor */
+ void OnTimeout() final {
+ assert(state == State::DELAY);
+
+ Connect();
+ }
+
+private:
+ EventLoop &GetEventLoop() {
+ return DeferredMonitor::GetEventLoop();
+ }
+
+ void SetState(State _state) {
+ assert(GetEventLoop().IsInside());
+
+ const ScopeLock protect(mutex);
+ state = _state;
+ cond.broadcast();
+ }
+
+ void SetState(State _state, const Error &error) {
+ assert(GetEventLoop().IsInside());
+
+ const ScopeLock protect(mutex);
+ state = _state;
+ last_error.Clear();
+ last_error.Set(error);
+ cond.broadcast();
+ }
+
+ void Connect() {
+ assert(state != State::READY);
+ assert(GetEventLoop().IsInside());
+
+ connection = &nfs_get_connection(server.c_str(),
+ export_name.c_str());
+ connection->AddLease(*this);
+
+ SetState(State::CONNECTING);
+ }
+
+ void EnsureConnected() {
+ if (state != State::READY)
+ Connect();
+ }
+
+ bool WaitConnected(Error &error) {
+ const ScopeLock protect(mutex);
+
+ while (true) {
+ switch (state) {
+ case State::INITIAL:
+ /* schedule connect */
+ mutex.unlock();
+ DeferredMonitor::Schedule();
+ mutex.lock();
+ break;
+
+ case State::CONNECTING:
+ case State::READY:
+ return true;
+
+ case State::DELAY:
+ assert(last_error.IsDefined());
+ error.Set(last_error);
+ return false;
+ }
+
+ cond.wait(mutex);
+ }
+ }
+
+ void Disconnect() {
+ assert(GetEventLoop().IsInside());
+
+ switch (state) {
+ case State::INITIAL:
+ DeferredMonitor::Cancel();
+ break;
+
+ case State::CONNECTING:
+ case State::READY:
+ connection->RemoveLease(*this);
+ SetState(State::INITIAL);
+ break;
+
+ case State::DELAY:
+ TimeoutMonitor::Cancel();
+ SetState(State::INITIAL);
+ break;
+ }
+ }
+};
+
+static std::string
+UriToNfsPath(const char *_uri_utf8, Error &error)
+{
+ assert(_uri_utf8 != nullptr);
+
+ /* libnfs paths must begin with a slash */
+ std::string uri_utf8("/");
+ uri_utf8.append(_uri_utf8);
+
+ return AllocatedPath::FromUTF8(uri_utf8.c_str(), error).Steal();
+}
+
+std::string
+NfsStorage::MapUTF8(const char *uri_utf8) const
+{
+ assert(uri_utf8 != nullptr);
+
+ if (*uri_utf8 == 0)
+ return base;
+
+ return PathTraitsUTF8::Build(base.c_str(), uri_utf8);
+}
+
+const char *
+NfsStorage::MapToRelativeUTF8(const char *uri_utf8) const
+{
+ return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
+}
+
+static void
+Copy(FileInfo &info, const struct stat &st)
+{
+ if (S_ISREG(st.st_mode))
+ info.type = FileInfo::Type::REGULAR;
+ else if (S_ISDIR(st.st_mode))
+ info.type = FileInfo::Type::DIRECTORY;
+ else
+ info.type = FileInfo::Type::OTHER;
+
+ info.size = st.st_size;
+ info.mtime = st.st_mtime;
+ info.device = st.st_dev;
+ info.inode = st.st_ino;
+}
+
+class NfsGetInfoOperation final : public BlockingNfsOperation {
+ const char *const path;
+ FileInfo &info;
+
+public:
+ NfsGetInfoOperation(NfsConnection &_connection, const char *_path,
+ FileInfo &_info)
+ :BlockingNfsOperation(_connection), path(_path), info(_info) {}
+
+protected:
+ bool Start(Error &_error) override {
+ return connection.Stat(path, *this, _error);
+ }
+
+ void HandleResult(gcc_unused unsigned status, void *data) override {
+ Copy(info, *(const struct stat *)data);
+ }
+};
+
+bool
+NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow,
+ FileInfo &info, Error &error)
+{
+ const std::string path = UriToNfsPath(uri_utf8, error);
+ if (path.empty())
+ return false;
+
+ if (!WaitConnected(error))
+ return nullptr;
+
+ NfsGetInfoOperation operation(*connection, path.c_str(), info);
+ return operation.Run(error);
+}
+
+gcc_pure
+static bool
+SkipNameFS(const char *name)
+{
+ return name[0] == '.' &&
+ (name[1] == 0 ||
+ (name[1] == '.' && name[2] == 0));
+}
+
+static void
+Copy(FileInfo &info, const struct nfsdirent &ent)
+{
+ switch (ent.type) {
+ case NF3REG:
+ info.type = FileInfo::Type::REGULAR;
+ break;
+
+ case NF3DIR:
+ info.type = FileInfo::Type::DIRECTORY;
+ break;
+
+ default:
+ info.type = FileInfo::Type::OTHER;
+ break;
+ }
+
+ info.size = ent.size;
+ info.mtime = ent.mtime.tv_sec;
+ info.device = 0;
+ info.inode = ent.inode;
+}
+
+class NfsListDirectoryOperation final : public BlockingNfsOperation {
+ const char *const path;
+
+ MemoryStorageDirectoryReader::List entries;
+
+public:
+ NfsListDirectoryOperation(NfsConnection &_connection,
+ const char *_path)
+ :BlockingNfsOperation(_connection), path(_path) {}
+
+ StorageDirectoryReader *ToReader() {
+ return new MemoryStorageDirectoryReader(std::move(entries));
+ }
+
+protected:
+ bool Start(Error &_error) override {
+ return connection.OpenDirectory(path, *this, _error);
+ }
+
+ void HandleResult(gcc_unused unsigned status, void *data) override {
+ struct nfsdir *const dir = (struct nfsdir *)data;
+
+ CollectEntries(dir);
+ connection.CloseDirectory(dir);
+ }
+
+private:
+ void CollectEntries(struct nfsdir *dir);
+};
+
+inline void
+NfsListDirectoryOperation::CollectEntries(struct nfsdir *dir)
+{
+ assert(entries.empty());
+
+ const struct nfsdirent *ent;
+ while ((ent = connection.ReadDirectory(dir)) != nullptr) {
+ const Path name_fs = Path::FromFS(ent->name);
+ if (SkipNameFS(name_fs.c_str()))
+ continue;
+
+ std::string name_utf8 = name_fs.ToUTF8();
+ if (name_utf8.empty())
+ /* ignore files whose name cannot be converted
+ to UTF-8 */
+ continue;
+
+ entries.emplace_front(std::move(name_utf8));
+ Copy(entries.front().info, *ent);
+ }
+}
+
+StorageDirectoryReader *
+NfsStorage::OpenDirectory(const char *uri_utf8, Error &error)
+{
+ const std::string path = UriToNfsPath(uri_utf8, error);
+ if (path.empty())
+ return nullptr;
+
+ if (!WaitConnected(error))
+ return nullptr;
+
+ NfsListDirectoryOperation operation(*connection, path.c_str());
+ if (!operation.Run(error))
+ return nullptr;
+
+ return operation.ToReader();
+}
+
+static Storage *
+CreateNfsStorageURI(EventLoop &event_loop, const char *base,
+ Error &error)
+{
+ if (memcmp(base, "nfs://", 6) != 0)
+ return nullptr;
+
+ const char *p = base + 6;
+
+ const char *mount = strchr(p, '/');
+ if (mount == nullptr) {
+ error.Set(nfs_domain, "Malformed nfs:// URI");
+ return nullptr;
+ }
+
+ const std::string server(p, mount);
+
+ nfs_set_base(server.c_str(), mount);
+
+ return new NfsStorage(event_loop, base, server.c_str(), mount);
+}
+
+const StoragePlugin nfs_storage_plugin = {
+ "nfs",
+ CreateNfsStorageURI,
+};
diff --git a/src/storage/plugins/NfsStorage.hxx b/src/storage/plugins/NfsStorage.hxx
new file mode 100644
index 000000000..f7e18effc
--- /dev/null
+++ b/src/storage/plugins/NfsStorage.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_NFS_HXX
+#define MPD_STORAGE_NFS_HXX
+
+#include "check.h"
+
+struct StoragePlugin;
+
+extern const StoragePlugin nfs_storage_plugin;
+
+#endif
diff --git a/src/storage/plugins/SmbclientStorage.cxx b/src/storage/plugins/SmbclientStorage.cxx
new file mode 100644
index 000000000..70a6e16bb
--- /dev/null
+++ b/src/storage/plugins/SmbclientStorage.cxx
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SmbclientStorage.hxx"
+#include "storage/StoragePlugin.hxx"
+#include "storage/StorageInterface.hxx"
+#include "storage/FileInfo.hxx"
+#include "lib/smbclient/Init.hxx"
+#include "lib/smbclient/Mutex.hxx"
+#include "fs/Traits.hxx"
+#include "util/Error.hxx"
+#include "thread/Mutex.hxx"
+
+#include <libsmbclient.h>
+
+class SmbclientDirectoryReader final : public StorageDirectoryReader {
+ const std::string base;
+ const unsigned handle;
+
+ const char *name;
+
+public:
+ SmbclientDirectoryReader(std::string &&_base, unsigned _handle)
+ :base(std::move(_base)), handle(_handle) {}
+
+ virtual ~SmbclientDirectoryReader();
+
+ /* virtual methods from class StorageDirectoryReader */
+ const char *Read() override;
+ bool GetInfo(bool follow, FileInfo &info, Error &error) override;
+};
+
+class SmbclientStorage final : public Storage {
+ const std::string base;
+
+ SMBCCTX *const ctx;
+
+public:
+ SmbclientStorage(const char *_base, SMBCCTX *_ctx)
+ :base(_base), ctx(_ctx) {}
+
+ virtual ~SmbclientStorage() {
+ smbclient_mutex.lock();
+ smbc_free_context(ctx, 1);
+ smbclient_mutex.unlock();
+ }
+
+ /* virtual methods from class Storage */
+ bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) override;
+
+ StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) override;
+
+ std::string MapUTF8(const char *uri_utf8) const override;
+
+ const char *MapToRelativeUTF8(const char *uri_utf8) const override;
+};
+
+std::string
+SmbclientStorage::MapUTF8(const char *uri_utf8) const
+{
+ assert(uri_utf8 != nullptr);
+
+ if (*uri_utf8 == 0)
+ return base;
+
+ return PathTraitsUTF8::Build(base.c_str(), uri_utf8);
+}
+
+const char *
+SmbclientStorage::MapToRelativeUTF8(const char *uri_utf8) const
+{
+ return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
+}
+
+static bool
+GetInfo(const char *path, FileInfo &info, Error &error)
+{
+ struct stat st;
+ smbclient_mutex.lock();
+ bool success = smbc_stat(path, &st) == 0;
+ smbclient_mutex.unlock();
+ if (!success) {
+ error.SetErrno();
+ return false;
+ }
+
+ if (S_ISREG(st.st_mode))
+ info.type = FileInfo::Type::REGULAR;
+ else if (S_ISDIR(st.st_mode))
+ info.type = FileInfo::Type::DIRECTORY;
+ else
+ info.type = FileInfo::Type::OTHER;
+
+ info.size = st.st_size;
+ info.mtime = st.st_mtime;
+ info.device = st.st_dev;
+ info.inode = st.st_ino;
+ return true;
+}
+
+bool
+SmbclientStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow,
+ FileInfo &info, Error &error)
+{
+ const std::string mapped = MapUTF8(uri_utf8);
+ return ::GetInfo(mapped.c_str(), info, error);
+}
+
+StorageDirectoryReader *
+SmbclientStorage::OpenDirectory(const char *uri_utf8, Error &error)
+{
+ std::string mapped = MapUTF8(uri_utf8);
+ smbclient_mutex.lock();
+ int handle = smbc_opendir(mapped.c_str());
+ smbclient_mutex.unlock();
+ if (handle < 0) {
+ error.SetErrno();
+ return nullptr;
+ }
+
+ return new SmbclientDirectoryReader(std::move(mapped.c_str()), handle);
+}
+
+gcc_pure
+static bool
+SkipNameFS(const char *name)
+{
+ return name[0] == '.' &&
+ (name[1] == 0 ||
+ (name[1] == '.' && name[2] == 0));
+}
+
+SmbclientDirectoryReader::~SmbclientDirectoryReader()
+{
+ smbclient_mutex.lock();
+ smbc_close(handle);
+ smbclient_mutex.unlock();
+}
+
+const char *
+SmbclientDirectoryReader::Read()
+{
+ const ScopeLock protect(smbclient_mutex);
+
+ struct smbc_dirent *e;
+ while ((e = smbc_readdir(handle)) != nullptr) {
+ name = e->name;
+ if (!SkipNameFS(name))
+ return name;
+ }
+
+ return nullptr;
+}
+
+bool
+SmbclientDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info,
+ Error &error)
+{
+ const std::string path = PathTraitsUTF8::Build(base.c_str(), name);
+ return ::GetInfo(path.c_str(), info, error);
+}
+
+static Storage *
+CreateSmbclientStorageURI(gcc_unused EventLoop &event_loop, const char *base,
+ Error &error)
+{
+ if (memcmp(base, "smb://", 6) != 0)
+ return nullptr;
+
+ if (!SmbclientInit(error))
+ return nullptr;
+
+ const ScopeLock protect(smbclient_mutex);
+ SMBCCTX *ctx = smbc_new_context();
+ if (ctx == nullptr) {
+ error.SetErrno("smbc_new_context() failed");
+ return nullptr;
+ }
+
+ SMBCCTX *ctx2 = smbc_init_context(ctx);
+ if (ctx2 == nullptr) {
+ error.SetErrno("smbc_init_context() failed");
+ smbc_free_context(ctx, 1);
+ return nullptr;
+ }
+
+ return new SmbclientStorage(base, ctx2);
+}
+
+const StoragePlugin smbclient_storage_plugin = {
+ "smbclient",
+ CreateSmbclientStorageURI,
+};
diff --git a/src/storage/plugins/SmbclientStorage.hxx b/src/storage/plugins/SmbclientStorage.hxx
new file mode 100644
index 000000000..7c198d920
--- /dev/null
+++ b/src/storage/plugins/SmbclientStorage.hxx
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_SMBCLIENT_HXX
+#define MPD_STORAGE_SMBCLIENT_HXX
+
+#include "check.h"
+
+struct StoragePlugin;
+
+extern const StoragePlugin smbclient_storage_plugin;
+
+#endif
diff --git a/src/system/Clock.cxx b/src/system/Clock.cxx
index 347997a44..9baa0c0ca 100644
--- a/src/system/Clock.cxx
+++ b/src/system/Clock.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,6 +31,28 @@
#endif
unsigned
+MonotonicClockS(void)
+{
+#ifdef WIN32
+ return GetTickCount() / 1000;
+#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
+ static mach_timebase_info_data_t base;
+ if (base.denom == 0)
+ (void)mach_timebase_info(&base);
+
+ return (unsigned)((mach_absolute_time() * base.numer / 1000)
+ / (1000000 * base.denom));
+#elif defined(CLOCK_MONOTONIC)
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec;
+#else
+ /* we have no monotonic clock, fall back to time() */
+ return time(nullptr);
+#endif
+}
+
+unsigned
MonotonicClockMS(void)
{
#ifdef WIN32
@@ -96,3 +118,29 @@ MonotonicClockUS(void)
#endif
}
+#ifdef WIN32
+
+gcc_const
+static unsigned
+DeltaFileTimeS(FILETIME a, FILETIME b)
+{
+ ULARGE_INTEGER a2, b2;
+ b2.LowPart = b.dwLowDateTime;
+ b2.HighPart = b.dwHighDateTime;
+ a2.LowPart = a.dwLowDateTime;
+ a2.HighPart = a.dwHighDateTime;
+ return (a2.QuadPart - b2.QuadPart) / 10000000;
+}
+
+unsigned
+GetProcessUptimeS()
+{
+ FILETIME creation_time, exit_time, kernel_time, user_time, now;
+ GetProcessTimes(GetCurrentProcess(), &creation_time,
+ &exit_time, &kernel_time, &user_time);
+ GetSystemTimeAsFileTime(&now);
+
+ return DeltaFileTimeS(now, creation_time);
+}
+
+#endif
diff --git a/src/system/Clock.hxx b/src/system/Clock.hxx
index 7be1127bf..333a41000 100644
--- a/src/system/Clock.hxx
+++ b/src/system/Clock.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,13 @@
#include <stdint.h>
/**
+ * Returns the value of a monotonic clock in seconds.
+ */
+gcc_pure
+unsigned
+MonotonicClockS();
+
+/**
* Returns the value of a monotonic clock in milliseconds.
*/
gcc_pure
@@ -38,4 +45,15 @@ gcc_pure
uint64_t
MonotonicClockUS();
+#ifdef WIN32
+
+/**
+ * Returns the uptime of the current process in seconds.
+ */
+gcc_pure
+unsigned
+GetProcessUptimeS();
+
+#endif
+
#endif
diff --git a/src/system/EPollFD.cxx b/src/system/EPollFD.cxx
index 5721c0194..43e74712f 100644
--- a/src/system/EPollFD.cxx
+++ b/src/system/EPollFD.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,21 @@
#include "EPollFD.hxx"
#include "FatalError.hxx"
+#ifdef __BIONIC__
+
+#include <sys/syscall.h>
+#include <fcntl.h>
+
+#define EPOLL_CLOEXEC O_CLOEXEC
+
+static inline int
+epoll_create1(int flags)
+{
+ return syscall(__NR_epoll_create1, flags);
+}
+
+#endif
+
EPollFD::EPollFD()
:fd(::epoll_create1(EPOLL_CLOEXEC))
{
diff --git a/src/system/EPollFD.hxx b/src/system/EPollFD.hxx
index 41f7ec377..8b9d7d2ba 100644
--- a/src/system/EPollFD.hxx
+++ b/src/system/EPollFD.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,6 +23,7 @@
#include <assert.h>
#include <sys/epoll.h>
#include <unistd.h>
+#include <stdint.h>
#include "check.h"
diff --git a/src/system/EventFD.cxx b/src/system/EventFD.cxx
index 3560bf8c3..9ac4c1d94 100644
--- a/src/system/EventFD.cxx
+++ b/src/system/EventFD.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/system/EventFD.hxx b/src/system/EventFD.hxx
index 67a0258ab..2a70461d9 100644
--- a/src/system/EventFD.hxx
+++ b/src/system/EventFD.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/system/EventPipe.cxx b/src/system/EventPipe.cxx
index b49d1d0f0..b8fc85aed 100644
--- a/src/system/EventPipe.cxx
+++ b/src/system/EventPipe.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/system/EventPipe.hxx b/src/system/EventPipe.hxx
index 86a10b0bb..42b3bb93d 100644
--- a/src/system/EventPipe.hxx
+++ b/src/system/EventPipe.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx
index f02b4b581..35e94f169 100644
--- a/src/system/FatalError.cxx
+++ b/src/system/FatalError.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,12 +23,15 @@
#include "util/Domain.hxx"
#include "LogV.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#ifdef WIN32
#include <windows.h>
@@ -76,19 +79,13 @@ FatalError(const char *msg, const Error &error)
}
void
-FatalError(const char *msg, GError *error)
-{
- FormatFatalError("%s: %s", msg, error->message);
-}
-
-void
FatalSystemError(const char *msg)
{
const char *system_error;
#ifdef WIN32
system_error = g_win32_error_message(GetLastError());
#else
- system_error = g_strerror(errno);
+ system_error = strerror(errno);
#endif
FormatError(fatal_error_domain, "%s: %s", msg, system_error);
diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx
index 2845359ef..d4698b3d9 100644
--- a/src/system/FatalError.hxx
+++ b/src/system/FatalError.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,6 @@
#define MPD_FATAL_ERROR_HXX
#include "check.h"
-#include "gerror.h"
#include "Compiler.h"
class Error;
@@ -45,10 +44,6 @@ gcc_noreturn
void
FatalError(const char *msg, const Error &error);
-gcc_noreturn
-void
-FatalError(const char *msg, GError *error);
-
/**
* Call this after a system call has failed that is not supposed to
* fail. Prints the given message, the system error message (from
diff --git a/src/system/PeriodClock.hxx b/src/system/PeriodClock.hxx
new file mode 100644
index 000000000..1ba74ece2
--- /dev/null
+++ b/src/system/PeriodClock.hxx
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PERIOD_CLOCK_HXX
+#define MPD_PERIOD_CLOCK_HXX
+
+#include "Clock.hxx"
+
+/**
+ * This is a stopwatch which saves the timestamp of an event, and can
+ * check whether a specified time span has passed since then.
+ */
+class PeriodClock {
+protected:
+ typedef unsigned Stamp;
+
+private:
+ Stamp last;
+
+public:
+ /**
+ * Initializes the object, setting the last time stamp to "0",
+ * i.e. a Check() will always succeed. If you do not want this
+ * default behaviour, call Update() immediately after creating the
+ * object.
+ */
+ constexpr
+ PeriodClock():last(0) {}
+
+protected:
+ static Stamp GetNow() {
+ return MonotonicClockMS();
+ }
+
+ constexpr int Elapsed(Stamp now) const {
+ return last == 0
+ ? -1
+ : now - last;
+ }
+
+ constexpr bool Check(Stamp now, unsigned duration) const {
+ return now >= last + duration;
+ }
+
+ void Update(Stamp now) {
+ last = now;
+ }
+
+public:
+ bool IsDefined() const {
+ return last != 0;
+ }
+
+ /**
+ * Resets the clock.
+ */
+ void Reset() {
+ last = 0;
+ }
+
+ /**
+ * Returns the number of milliseconds elapsed since the last
+ * update(). Returns -1 if update() was never called.
+ */
+ int Elapsed() const {
+ return Elapsed(GetNow());
+ }
+
+ /**
+ * Combines a call to Elapsed() and Update().
+ */
+ int ElapsedUpdate() {
+ const auto now = GetNow();
+ int result = Elapsed(now);
+ Update(now);
+ return result;
+ }
+
+ /**
+ * Checks whether the specified duration has passed since the last
+ * update.
+ *
+ * @param duration the duration in milliseconds
+ */
+ bool Check(unsigned duration) const {
+ return Check(GetNow(), duration);
+ }
+
+ /**
+ * Updates the time stamp, setting it to the current clock.
+ */
+ void Update() {
+ Update(GetNow());
+ }
+
+ /**
+ * Updates the time stamp, setting it to the current clock plus the
+ * specified offset.
+ */
+ void UpdateWithOffset(int offset) {
+ Update(GetNow() + offset);
+ }
+
+ /**
+ * Checks whether the specified duration has passed since the last
+ * update. If yes, it updates the time stamp.
+ *
+ * @param duration the duration in milliseconds
+ */
+ bool CheckUpdate(unsigned duration) {
+ Stamp now = GetNow();
+ if (Check(now, duration)) {
+ Update(now);
+ return true;
+ } else
+ return false;
+ }
+
+ /**
+ * Checks whether the specified duration has passed since the last
+ * update. After that, it updates the time stamp.
+ *
+ * @param duration the duration in milliseconds
+ */
+ bool CheckAlwaysUpdate(unsigned duration) {
+ Stamp now = GetNow();
+ bool ret = Check(now, duration);
+ Update(now);
+ return ret;
+ }
+};
+
+#endif
diff --git a/src/system/Resolver.cxx b/src/system/Resolver.cxx
index 5e6ea590b..a94217bac 100644
--- a/src/system/Resolver.cxx
+++ b/src/system/Resolver.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,11 +22,12 @@
#include "util/Error.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
-
#ifndef WIN32
#include <sys/socket.h>
#include <netdb.h>
+#ifdef HAVE_TCP
+#include <netinet/in.h>
+#endif
#else
#include <ws2tcpip.h>
#include <winsock.h>
@@ -41,17 +42,17 @@
const Domain resolver_domain("resolver");
-char *
-sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error)
+std::string
+sockaddr_to_string(const struct sockaddr *sa, size_t length)
{
#ifdef HAVE_UN
if (sa->sa_family == AF_UNIX) {
/* return path of UNIX domain sockets */
const sockaddr_un &s_un = *(const sockaddr_un *)sa;
if (length < sizeof(s_un) || s_un.sun_path[0] == 0)
- return g_strdup("local");
+ return "local";
- return g_strdup(s_un.sun_path);
+ return s_un.sun_path;
}
#endif
@@ -80,17 +81,23 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error)
ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv),
NI_NUMERICHOST|NI_NUMERICSERV);
- if (ret != 0) {
- error.Set(resolver_domain, ret, gai_strerror(ret));
- return NULL;
- }
+ if (ret != 0)
+ return "unknown";
#ifdef HAVE_IPV6
- if (strchr(host, ':') != NULL)
- return g_strconcat("[", host, "]:", serv, NULL);
+ if (strchr(host, ':') != nullptr) {
+ std::string result("[");
+ result.append(host);
+ result.append("]:");
+ result.append(serv);
+ return result;
+ }
#endif
- return g_strconcat(host, ":", serv, NULL);
+ std::string result(host);
+ result.push_back(':');
+ result.append(serv);
+ return result;
}
struct addrinfo *
@@ -98,41 +105,42 @@ resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
Error &error)
{
- char *p = g_strdup(host_port);
- const char *host = p, *port = NULL;
+ std::string p(host_port);
+ const char *host = p.c_str(), *port = nullptr;
if (host_port[0] == '[') {
/* IPv6 needs enclosing square braces, to
differentiate between IP colons and the port
separator */
- char *q = strchr(p + 1, ']');
- if (q != NULL && q[1] == ':' && q[2] != 0) {
- *q = 0;
+ size_t q = p.find(']', 1);
+ if (q != p.npos && p[q + 1] == ':' && p[q + 2] != 0) {
+ p[q] = 0;
+ port = host + q + 2;
++host;
- port = q + 2;
}
}
- if (port == NULL) {
+ if (port == nullptr) {
/* port is after the colon, but only if it's the only
colon (don't split IPv6 addresses) */
- char *q = strchr(p, ':');
- if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) {
- *q = 0;
- port = q + 1;
+ auto q = p.find(':');
+ if (q != p.npos && p[q + 1] != 0 &&
+ p.find(':', q + 1) == p.npos) {
+ p[q] = 0;
+ port = host + q + 1;
}
}
char buffer[32];
- if (port == NULL && default_port != 0) {
+ if (port == nullptr && default_port != 0) {
snprintf(buffer, sizeof(buffer), "%u", default_port);
port = buffer;
}
if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0)
- host = NULL;
+ host = nullptr;
addrinfo hints;
memset(&hints, 0, sizeof(hints));
@@ -142,12 +150,11 @@ resolve_host_port(const char *host_port, unsigned default_port,
struct addrinfo *ai;
int ret = getaddrinfo(host, port, &hints, &ai);
- g_free(p);
if (ret != 0) {
error.Format(resolver_domain, ret,
"Failed to look up '%s': %s",
host_port, gai_strerror(ret));
- return NULL;
+ return nullptr;
}
return ai;
diff --git a/src/system/Resolver.hxx b/src/system/Resolver.hxx
index 62ef455a1..54922d98f 100644
--- a/src/system/Resolver.hxx
+++ b/src/system/Resolver.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,27 +22,27 @@
#include "Compiler.h"
+#include <string>
+
#include <stddef.h>
struct sockaddr;
struct addrinfo;
class Error;
+class Domain;
-extern const class Domain resolver_domain;
+extern const Domain resolver_domain;
/**
* Converts the specified socket address into a string in the form
- * "IP:PORT". The return value must be freed with g_free() when you
- * don't need it anymore.
+ * "IP:PORT".
*
* @param sa the sockaddr struct
* @param length the length of #sa in bytes
- * @param error location to store the error occurring, or NULL to
- * ignore errors
*/
-gcc_malloc
-char *
-sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error);
+gcc_pure
+std::string
+sockaddr_to_string(const sockaddr *sa, size_t length);
/**
* Resolve a specification in the form "host", "host:port",
@@ -54,7 +54,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error);
* @return an #addrinfo linked list that must be freed with
* freeaddrinfo(), or NULL on error
*/
-struct addrinfo *
+addrinfo *
resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
Error &error);
diff --git a/src/system/SignalFD.cxx b/src/system/SignalFD.cxx
index b89775dcd..173a0cc8c 100644
--- a/src/system/SignalFD.cxx
+++ b/src/system/SignalFD.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,6 @@
#include "config.h"
#ifdef USE_SIGNALFD
#include "SignalFD.hxx"
-#include "fd_util.h"
#include "FatalError.hxx"
#include <assert.h>
diff --git a/src/system/SignalFD.hxx b/src/system/SignalFD.hxx
index 7163782d1..11bf30f74 100644
--- a/src/system/SignalFD.hxx
+++ b/src/system/SignalFD.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/system/SocketError.cxx b/src/system/SocketError.cxx
index 315a86e1f..e138f4dd3 100644
--- a/src/system/SocketError.cxx
+++ b/src/system/SocketError.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
#include "SocketError.hxx"
#include "util/Domain.hxx"
-#include <glib.h>
+#include <string.h>
const Domain socket_domain("socket");
@@ -41,6 +41,6 @@ SocketErrorMessage::SocketErrorMessage(socket_error_t code)
#else
SocketErrorMessage::SocketErrorMessage(socket_error_t code)
- :msg(g_strerror(code)) {}
+ :msg(strerror(code)) {}
#endif
diff --git a/src/system/SocketError.hxx b/src/system/SocketError.hxx
index 22fbd2441..01abc9884 100644
--- a/src/system/SocketError.hxx
+++ b/src/system/SocketError.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
#define MPD_SOCKET_ERROR_HXX
#include "Compiler.h"
-#include "util/Error.hxx"
+#include "util/Error.hxx" // IWYU pragma: export
#ifdef WIN32
#include <winsock2.h>
@@ -31,11 +31,13 @@ typedef DWORD socket_error_t;
typedef int socket_error_t;
#endif
+class Domain;
+
/**
* A #Domain for #Error for socket I/O errors. The code is an errno
* value (or WSAGetLastError() on Windows).
*/
-extern const class Domain socket_domain;
+extern const Domain socket_domain;
gcc_pure
static inline socket_error_t
diff --git a/src/system/SocketUtil.cxx b/src/system/SocketUtil.cxx
index 9c4002386..b9df0d59d 100644
--- a/src/system/SocketUtil.cxx
+++ b/src/system/SocketUtil.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,8 +22,6 @@
#include "SocketError.hxx"
#include "fd_util.h"
-#include <glib.h>
-
#include <unistd.h>
#ifndef WIN32
diff --git a/src/system/SocketUtil.hxx b/src/system/SocketUtil.hxx
index 5e582ec0d..652788759 100644
--- a/src/system/SocketUtil.hxx
+++ b/src/system/SocketUtil.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/system/fd_util.c b/src/system/fd_util.c
index b4a7032e6..b53ecda00 100644
--- a/src/system/fd_util.c
+++ b/src/system/fd_util.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
@@ -29,10 +29,6 @@
#include "config.h" /* must be first for large file support */
#include "fd_util.h"
-#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
-#define _GNU_SOURCE
-#endif
-
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
@@ -73,7 +69,7 @@ fd_mask_flags(int fd, int and_mask, int xor_mask)
#endif /* !WIN32 */
-static int
+int
fd_set_cloexec(int fd, bool enable)
{
#ifndef WIN32
diff --git a/src/system/fd_util.h b/src/system/fd_util.h
index b7a9a6dd3..f4a940e91 100644
--- a/src/system/fd_util.h
+++ b/src/system/fd_util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
@@ -42,10 +42,6 @@
#include <stddef.h>
#ifndef WIN32
-#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
-#define _GNU_SOURCE
-#endif
-
#include <sys/types.h>
#endif
@@ -55,6 +51,9 @@ struct sockaddr;
extern "C" {
#endif
+int
+fd_set_cloexec(int fd, bool enable);
+
/**
* Wrapper for dup(), which sets the CLOEXEC flag on the new
* descriptor.
diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx
index 73e46e49f..c2498c9e9 100644
--- a/src/tag/Aiff.cxx
+++ b/src/tag/Aiff.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,9 +26,7 @@
#include <limits>
#include <stdint.h>
-#include <sys/types.h>
#include <sys/stat.h>
-#include <unistd.h>
#include <string.h>
static constexpr Domain aiff_domain("aiff");
diff --git a/src/tag/Aiff.hxx b/src/tag/Aiff.hxx
index 9000be7f8..cd323ee2e 100644
--- a/src/tag/Aiff.hxx
+++ b/src/tag/Aiff.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/ApeLoader.cxx b/src/tag/ApeLoader.cxx
index 8251efe10..f473c910e 100644
--- a/src/tag/ApeLoader.cxx
+++ b/src/tag/ApeLoader.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,8 +22,6 @@
#include "system/ByteOrder.hxx"
#include "fs/FileSystem.hxx"
-#include <glib.h>
-
#include <stdint.h>
#include <assert.h>
#include <stdio.h>
@@ -61,9 +59,9 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
remaining -= sizeof(footer);
assert(remaining > 10);
- char *buffer = (char *)g_malloc(remaining);
+ char *buffer = new char[remaining];
if (fread(buffer, 1, remaining, fp) != remaining) {
- g_free(buffer);
+ delete[] buffer;
return false;
}
@@ -98,7 +96,7 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback)
remaining -= size;
}
- g_free(buffer);
+ delete[] buffer;
return true;
}
diff --git a/src/tag/ApeLoader.hxx b/src/tag/ApeLoader.hxx
index 915c363b4..ce82cc35d 100644
--- a/src/tag/ApeLoader.hxx
+++ b/src/tag/ApeLoader.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/ApeReplayGain.cxx b/src/tag/ApeReplayGain.cxx
index cc65fb79d..345f45710 100644
--- a/src/tag/ApeReplayGain.cxx
+++ b/src/tag/ApeReplayGain.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,7 @@
#include "config.h"
#include "ApeReplayGain.hxx"
#include "ApeLoader.hxx"
-#include "ReplayGainInfo.hxx"
+#include "ReplayGain.hxx"
#include "util/ASCII.hxx"
#include "fs/Path.hxx"
@@ -43,20 +43,7 @@ replay_gain_ape_callback(unsigned long flags, const char *key,
memcpy(value, _value, value_length);
value[value_length] = 0;
- if (StringEqualsCaseASCII(key, "replaygain_track_gain")) {
- info.tuples[REPLAY_GAIN_TRACK].gain = atof(value);
- return true;
- } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) {
- info.tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
- return true;
- } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) {
- info.tuples[REPLAY_GAIN_TRACK].peak = atof(value);
- return true;
- } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) {
- info.tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
- return true;
- } else
- return false;
+ return ParseReplayGainTag(info, key, value);
}
bool
diff --git a/src/tag/ApeReplayGain.hxx b/src/tag/ApeReplayGain.hxx
index 865add6f1..03c899c5c 100644
--- a/src/tag/ApeReplayGain.hxx
+++ b/src/tag/ApeReplayGain.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/ApeTag.cxx b/src/tag/ApeTag.cxx
index 2df53947a..f714a1624 100644
--- a/src/tag/ApeTag.cxx
+++ b/src/tag/ApeTag.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/ApeTag.hxx b/src/tag/ApeTag.hxx
index e35edc381..edebf076c 100644
--- a/src/tag/ApeTag.hxx
+++ b/src/tag/ApeTag.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -34,6 +34,6 @@ extern const struct tag_table ape_tags[];
*/
bool
tag_ape_scan2(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/tag/MixRamp.cxx b/src/tag/MixRamp.cxx
new file mode 100644
index 000000000..e1b6e43c5
--- /dev/null
+++ b/src/tag/MixRamp.cxx
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "MixRamp.hxx"
+#include "VorbisComment.hxx"
+#include "MixRampInfo.hxx"
+#include "util/ASCII.hxx"
+
+#include <assert.h>
+
+template<typename T>
+static bool
+ParseMixRampTagTemplate(MixRampInfo &info, const T t)
+{
+ const char *value;
+
+ if ((value = t["mixramp_start"]) != nullptr) {
+ info.SetStart(value);
+ return true;
+ } else if ((value = t["mixramp_end"]) != nullptr) {
+ info.SetEnd(value);
+ return true;
+ } else
+ return false;
+
+}
+
+bool
+ParseMixRampTag(MixRampInfo &info, const char *name, const char *value)
+{
+ assert(name != nullptr);
+ assert(value != nullptr);
+
+ struct NameValue {
+ const char *name;
+ const char *value;
+
+ gcc_pure
+ const char *operator[](const char *n) const {
+ return StringEqualsCaseASCII(name, n)
+ ? value
+ : nullptr;
+ }
+ };
+
+ return ParseMixRampTagTemplate(info, NameValue{name, value});
+}
+
+bool
+ParseMixRampVorbis(MixRampInfo &info, const char *entry)
+{
+ struct VorbisCommentEntry {
+ const char *entry;
+
+ gcc_pure
+ const char *operator[](const char *n) const {
+ return vorbis_comment_value(entry, n);
+ }
+ };
+
+ return ParseMixRampTagTemplate(info, VorbisCommentEntry{entry});
+}
diff --git a/src/tag/MixRamp.hxx b/src/tag/MixRamp.hxx
new file mode 100644
index 000000000..5b4e2dc30
--- /dev/null
+++ b/src/tag/MixRamp.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_MIXRAMP_HXX
+#define MPD_TAG_MIXRAMP_HXX
+
+#include "check.h"
+
+class MixRampInfo;
+
+bool
+ParseMixRampTag(MixRampInfo &info, const char *name, const char *value);
+
+bool
+ParseMixRampVorbis(MixRampInfo &info, const char *entry);
+
+#endif
diff --git a/src/tag/ReplayGain.cxx b/src/tag/ReplayGain.cxx
new file mode 100644
index 000000000..83a48f243
--- /dev/null
+++ b/src/tag/ReplayGain.cxx
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ReplayGain.hxx"
+#include "VorbisComment.hxx"
+#include "ReplayGainInfo.hxx"
+#include "util/ASCII.hxx"
+
+#include <assert.h>
+#include <stdlib.h>
+
+template<typename T>
+static bool
+ParseReplayGainTagTemplate(ReplayGainInfo &info, const T t)
+{
+ const char *value;
+
+ if ((value = t["replaygain_track_gain"]) != nullptr) {
+ info.tuples[REPLAY_GAIN_TRACK].gain = atof(value);
+ return true;
+ } else if ((value = t["replaygain_album_gain"]) != nullptr) {
+ info.tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
+ return true;
+ } else if ((value = t["replaygain_track_peak"]) != nullptr) {
+ info.tuples[REPLAY_GAIN_TRACK].peak = atof(value);
+ return true;
+ } else if ((value = t["replaygain_album_peak"]) != nullptr) {
+ info.tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
+ return true;
+ } else
+ return false;
+
+}
+
+bool
+ParseReplayGainTag(ReplayGainInfo &info, const char *name, const char *value)
+{
+ assert(name != nullptr);
+ assert(value != nullptr);
+
+ struct NameValue {
+ const char *name;
+ const char *value;
+
+ gcc_pure
+ const char *operator[](const char *n) const {
+ return StringEqualsCaseASCII(name, n)
+ ? value
+ : nullptr;
+ }
+ };
+
+ return ParseReplayGainTagTemplate(info, NameValue{name, value});
+}
+
+bool
+ParseReplayGainVorbis(ReplayGainInfo &info, const char *entry)
+{
+ struct VorbisCommentEntry {
+ const char *entry;
+
+ gcc_pure
+ const char *operator[](const char *n) const {
+ return vorbis_comment_value(entry, n);
+ }
+ };
+
+ return ParseReplayGainTagTemplate(info, VorbisCommentEntry{entry});
+}
diff --git a/src/tag/ReplayGain.hxx b/src/tag/ReplayGain.hxx
new file mode 100644
index 000000000..2bf5e0db1
--- /dev/null
+++ b/src/tag/ReplayGain.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_REPLAY_GAIN_HXX
+#define MPD_TAG_REPLAY_GAIN_HXX
+
+#include "check.h"
+
+struct ReplayGainInfo;
+
+bool
+ParseReplayGainTag(ReplayGainInfo &info, const char *name, const char *value);
+
+bool
+ParseReplayGainVorbis(ReplayGainInfo &info, const char *entry);
+
+#endif
diff --git a/src/tag/Riff.cxx b/src/tag/Riff.cxx
index ac162bc24..c630f082d 100644
--- a/src/tag/Riff.cxx
+++ b/src/tag/Riff.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,7 +26,6 @@
#include <limits>
#include <stdint.h>
-#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
diff --git a/src/tag/Riff.hxx b/src/tag/Riff.hxx
index fbbdfaaf6..a9af67b7a 100644
--- a/src/tag/Riff.hxx
+++ b/src/tag/Riff.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx
new file mode 100644
index 000000000..6a55a450f
--- /dev/null
+++ b/src/tag/Set.cxx
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Set.hxx"
+#include "TagBuilder.hxx"
+#include "TagSettings.h"
+
+#include <assert.h>
+
+/**
+ * Copy all tag items of the specified type.
+ */
+static bool
+CopyTagItem(TagBuilder &dest, TagType dest_type,
+ const Tag &src, TagType src_type)
+{
+ bool found = false;
+
+ for (const auto &item : src) {
+ if (item.type == src_type) {
+ dest.AddItem(dest_type, item.value);
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+/**
+ * Copy all tag items of the specified type. Fall back to "Artist" if
+ * there is no "AlbumArtist".
+ */
+static void
+CopyTagItem(TagBuilder &dest, const Tag &src, TagType type)
+{
+ if (!CopyTagItem(dest, type, src, type) &&
+ type == TAG_ALBUM_ARTIST)
+ CopyTagItem(dest, type, src, TAG_ARTIST);
+}
+
+/**
+ * Copy all tag items of the types in the mask.
+ */
+static void
+CopyTagMask(TagBuilder &dest, const Tag &src, uint32_t mask)
+{
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if ((mask & (1u << i)) != 0)
+ CopyTagItem(dest, src, TagType(i));
+}
+
+void
+TagSet::InsertUnique(const Tag &src, TagType type, const char *value,
+ uint32_t group_mask)
+{
+ TagBuilder builder;
+ if (value == nullptr)
+ builder.AddEmptyItem(type);
+ else
+ builder.AddItem(type, value);
+ CopyTagMask(builder, src, group_mask);
+#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
+ emplace(builder.Commit());
+#else
+ insert(builder.Commit());
+#endif
+}
+
+bool
+TagSet::CheckUnique(TagType dest_type,
+ const Tag &tag, TagType src_type,
+ uint32_t group_mask)
+{
+ bool found = false;
+
+ for (const auto &item : tag) {
+ if (item.type == src_type) {
+ InsertUnique(tag, dest_type, item.value, group_mask);
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+void
+TagSet::InsertUnique(const Tag &tag,
+ TagType type, uint32_t group_mask)
+{
+ static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES,
+ "Mask is too small");
+
+ assert((group_mask & (1u << unsigned(type))) == 0);
+
+ if (!CheckUnique(type, tag, type, group_mask) &&
+ (type != TAG_ALBUM_ARTIST ||
+ ignore_tag_items[TAG_ALBUM_ARTIST] ||
+ /* fall back to "Artist" if no "AlbumArtist" was found */
+ !CheckUnique(type, tag, TAG_ARTIST, group_mask)))
+ InsertUnique(tag, type, nullptr, group_mask);
+}
diff --git a/src/tag/Set.hxx b/src/tag/Set.hxx
new file mode 100644
index 000000000..b5acfcb36
--- /dev/null
+++ b/src/tag/Set.hxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_SET_HXX
+#define MPD_TAG_SET_HXX
+
+#include "Compiler.h"
+#include "Tag.hxx"
+
+#include <set>
+
+#include <string.h>
+#include <stdint.h>
+
+/**
+ * Helper class for #TagSet which compares two #Tag objects.
+ */
+struct TagLess {
+ gcc_pure
+ bool operator()(const Tag &a, const Tag &b) const {
+ if (a.num_items != b.num_items)
+ return a.num_items < b.num_items;
+
+ const unsigned n = a.num_items;
+ for (unsigned i = 0; i < n; ++i) {
+ const TagItem &ai = *a.items[i];
+ const TagItem &bi = *b.items[i];
+ if (ai.type != bi.type)
+ return unsigned(ai.type) < unsigned(bi.type);
+
+ const int cmp = strcmp(ai.value, bi.value);
+ if (cmp != 0)
+ return cmp < 0;
+ }
+
+ return false;
+ }
+};
+
+/**
+ * A set of #Tag objects.
+ */
+class TagSet : public std::set<Tag, TagLess> {
+public:
+ void InsertUnique(const Tag &tag,
+ TagType type, uint32_t group_mask);
+
+private:
+ void InsertUnique(const Tag &src, TagType type, const char *value,
+ uint32_t group_mask);
+
+ bool CheckUnique(TagType dest_type,
+ const Tag &tag, TagType src_type,
+ uint32_t group_mask);
+};
+
+#endif
diff --git a/src/tag/Tag.cxx b/src/tag/Tag.cxx
index 6bf070429..92ac4214c 100644
--- a/src/tag/Tag.cxx
+++ b/src/tag/Tag.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,9 +22,9 @@
#include "TagPool.hxx"
#include "TagString.hxx"
#include "TagSettings.h"
+#include "TagBuilder.hxx"
#include "util/ASCII.hxx"
-#include <glib.h>
#include <assert.h>
#include <string.h>
@@ -58,16 +58,10 @@ tag_name_parse_i(const char *name)
return TAG_NUM_OF_ITEM_TYPES;
}
-static size_t
-items_size(const Tag &tag)
-{
- return tag.num_items * sizeof(TagItem *);
-}
-
void
Tag::Clear()
{
- time = -1;
+ duration = SignedSongTime::Negative();
has_playlist = false;
tag_pool_lock.lock();
@@ -75,28 +69,18 @@ Tag::Clear()
tag_pool_put_item(items[i]);
tag_pool_lock.unlock();
- g_free(items);
+ delete[] items;
items = nullptr;
num_items = 0;
}
-Tag::~Tag()
-{
- tag_pool_lock.lock();
- for (int i = num_items; --i >= 0; )
- tag_pool_put_item(items[i]);
- tag_pool_lock.unlock();
-
- g_free(items);
-}
-
Tag::Tag(const Tag &other)
- :time(other.time), has_playlist(other.has_playlist),
- items(nullptr),
- num_items(other.num_items)
+ :duration(other.duration), has_playlist(other.has_playlist),
+ num_items(other.num_items),
+ items(nullptr)
{
if (num_items > 0) {
- items = (TagItem **)g_malloc(items_size(other));
+ items = new TagItem *[num_items];
tag_pool_lock.lock();
for (unsigned i = 0; i < num_items; i++)
@@ -108,46 +92,9 @@ Tag::Tag(const Tag &other)
Tag *
Tag::Merge(const Tag &base, const Tag &add)
{
- unsigned n;
-
- /* allocate new tag object */
-
- Tag *ret = new Tag();
- ret->time = add.time > 0 ? add.time : base.time;
- ret->num_items = base.num_items + add.num_items;
- ret->items = ret->num_items > 0
- ? (TagItem **)g_malloc(items_size(*ret))
- : nullptr;
-
- tag_pool_lock.lock();
-
- /* copy all items from "add" */
-
- for (unsigned i = 0; i < add.num_items; ++i)
- ret->items[i] = tag_pool_dup_item(add.items[i]);
-
- n = add.num_items;
-
- /* copy additional items from "base" */
-
- for (unsigned i = 0; i < base.num_items; ++i)
- if (!add.HasType(base.items[i]->type))
- ret->items[n++] = tag_pool_dup_item(base.items[i]);
-
- tag_pool_lock.unlock();
-
- assert(n <= ret->num_items);
-
- if (n < ret->num_items) {
- /* some tags were not copied - shrink ret->items */
- assert(n > 0);
-
- ret->num_items = n;
- ret->items = (TagItem **)
- g_realloc(ret->items, items_size(*ret));
- }
-
- return ret;
+ TagBuilder builder(add);
+ builder.Complement(base);
+ return builder.CommitNew();
}
Tag *
@@ -171,9 +118,9 @@ Tag::GetValue(TagType 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;
+ for (const auto &item : *this)
+ if (item.type == type)
+ return item.value;
return nullptr;
}
@@ -183,43 +130,3 @@ Tag::HasType(TagType type) const
{
return GetValue(type) != nullptr;
}
-
-void
-Tag::AddItemInternal(TagType type, const char *value, size_t len)
-{
- unsigned int i = num_items;
-
- char *p = FixTagString(value, len);
- if (p != nullptr) {
- value = p;
- len = strlen(value);
- }
-
- num_items++;
-
- items = (TagItem **)g_realloc(items, items_size(*this));
-
- tag_pool_lock.lock();
- items[i] = tag_pool_get_item(type, value, len);
- tag_pool_lock.unlock();
-
- g_free(p);
-}
-
-void
-Tag::AddItem(TagType type, const char *value, size_t len)
-{
- if (ignore_tag_items[type])
- return;
-
- if (value == nullptr || len == 0)
- return;
-
- AddItemInternal(type, value, len);
-}
-
-void
-Tag::AddItem(TagType type, const char *value)
-{
- AddItem(type, value, strlen(value));
-}
diff --git a/src/tag/Tag.hxx b/src/tag/Tag.hxx
index 5846e7a9d..f1d3d5767 100644
--- a/src/tag/Tag.hxx
+++ b/src/tag/Tag.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,13 @@
#ifndef MPD_TAG_HXX
#define MPD_TAG_HXX
-#include "TagType.h"
-#include "TagItem.hxx"
+#include "TagType.h" // IWYU pragma: export
+#include "TagItem.hxx" // IWYU pragma: export
+#include "Chrono.hxx"
#include "Compiler.h"
#include <algorithm>
+#include <iterator>
#include <stddef.h>
@@ -34,12 +36,10 @@
*/
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.
+ * The duration of the song. A negative value means that the
+ * length is unknown.
*/
- int time;
+ SignedSongTime duration;
/**
* Does this file have an embedded playlist (e.g. embedded CUE
@@ -47,23 +47,23 @@ struct Tag {
*/
bool has_playlist;
+ /** the total number of tag items in the #items array */
+ unsigned short num_items;
+
/** 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():duration(SignedSongTime::Negative()), has_playlist(false),
+ num_items(0), items(nullptr) {}
Tag(const Tag &other);
Tag(Tag &&other)
- :time(other.time), has_playlist(other.has_playlist),
- items(other.items), num_items(other.num_items) {
+ :duration(other.duration), has_playlist(other.has_playlist),
+ num_items(other.num_items), items(other.items) {
other.items = nullptr;
other.num_items = 0;
}
@@ -71,12 +71,14 @@ struct Tag {
/**
* Free the tag object and all its items.
*/
- ~Tag();
+ ~Tag() {
+ Clear();
+ }
Tag &operator=(const Tag &other) = delete;
Tag &operator=(Tag &&other) {
- time = other.time;
+ duration = other.duration;
has_playlist = other.has_playlist;
std::swap(items, other.items);
std::swap(num_items, other.num_items);
@@ -84,8 +86,8 @@ struct Tag {
}
/**
- * Returns true if the tag contains no items. This ignores the "time"
- * attribute.
+ * Returns true if the tag contains no items. This ignores
+ * the "duration" attribute.
*/
bool IsEmpty() const {
return num_items == 0;
@@ -95,7 +97,7 @@ struct Tag {
* Returns true if the tag contains any information.
*/
bool IsDefined() const {
- return !IsEmpty() || time >= 0;
+ return !IsEmpty() || !duration.IsNegative();
}
/**
@@ -104,24 +106,6 @@ struct Tag {
void Clear();
/**
- * Appends a new tag item.
- *
- * @param type the type of the new tag item
- * @param value the value of the tag item (not null-terminated)
- * @param len the length of #value
- */
- void AddItem(TagType type, const char *value, size_t len);
-
- /**
- * Appends a new tag item.
- *
- * @param tag the #tag object
- * @param type the type of the new tag item
- * @param value the value of the tag item (null-terminated)
- */
- void AddItem(TagType type, const char *value);
-
- /**
* Merges the data from two tags. If both tags share data for the
* same TagType, only data from "add" is used.
*
@@ -153,8 +137,58 @@ struct Tag {
gcc_pure
bool HasType(TagType type) const;
-private:
- void AddItemInternal(TagType type, const char *value, size_t len);
+ class const_iterator {
+ friend struct Tag;
+ const TagItem *const*cursor;
+
+ constexpr const_iterator(const TagItem *const*_cursor)
+ :cursor(_cursor) {}
+
+ public:
+ constexpr const TagItem &operator*() const {
+ return **cursor;
+ }
+
+ constexpr const TagItem *operator->() const {
+ return *cursor;
+ }
+
+ const_iterator &operator++() {
+ ++cursor;
+ return *this;
+ }
+
+ const_iterator operator++(int) {
+ auto result = cursor++;
+ return const_iterator{result};
+ }
+
+ const_iterator &operator--() {
+ --cursor;
+ return *this;
+ }
+
+ const_iterator operator--(int) {
+ auto result = cursor--;
+ return const_iterator{result};
+ }
+
+ constexpr bool operator==(const_iterator other) const {
+ return cursor == other.cursor;
+ }
+
+ constexpr bool operator!=(const_iterator other) const {
+ return cursor != other.cursor;
+ }
+ };
+
+ const_iterator begin() const {
+ return const_iterator{items};
+ }
+
+ const_iterator end() const {
+ return const_iterator{items + num_items};
+ }
};
/**
diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx
index 083b43d69..93518f6e9 100644
--- a/src/tag/TagBuilder.cxx
+++ b/src/tag/TagBuilder.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,24 +23,92 @@
#include "TagPool.hxx"
#include "TagString.hxx"
#include "Tag.hxx"
-
-#include <glib.h>
+#include "util/WritableBuffer.hxx"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
-void
-TagBuilder::Clear()
+TagBuilder::TagBuilder(const Tag &other)
+ :duration(other.duration), has_playlist(other.has_playlist)
{
- time = -1;
- has_playlist = false;
+ items.reserve(other.num_items);
tag_pool_lock.lock();
+ for (unsigned i = 0, n = other.num_items; i != n; ++i)
+ items.push_back(tag_pool_dup_item(other.items[i]));
+ tag_pool_lock.unlock();
+}
+
+TagBuilder::TagBuilder(Tag &&other)
+ :duration(other.duration), has_playlist(other.has_playlist)
+{
+ /* move all TagItem pointers from the Tag object; we don't
+ need to contact the tag pool, because all we do is move
+ references */
+ items.reserve(other.num_items);
+ std::copy_n(other.items, other.num_items, std::back_inserter(items));
+
+ /* discard the pointers from the Tag object */
+ other.num_items = 0;
+ delete[] other.items;
+ other.items = nullptr;
+}
+
+TagBuilder &
+TagBuilder::operator=(const TagBuilder &other)
+{
+ /* copy all attributes */
+ duration = other.duration;
+ has_playlist = other.has_playlist;
+ items = other.items;
+
+ /* increment the tag pool refcounters */
+ tag_pool_lock.lock();
for (auto i : items)
- tag_pool_put_item(i);
+ tag_pool_dup_item(i);
tag_pool_lock.unlock();
+ return *this;
+}
+
+TagBuilder &
+TagBuilder::operator=(TagBuilder &&other)
+{
+ duration = other.duration;
+ has_playlist = other.has_playlist;
+ items = std::move(other.items);
+
+ return *this;
+}
+
+TagBuilder &
+TagBuilder::operator=(Tag &&other)
+{
+ duration = other.duration;
+ has_playlist = other.has_playlist;
+
+ /* move all TagItem pointers from the Tag object; we don't
+ need to contact the tag pool, because all we do is move
+ references */
items.clear();
+ items.reserve(other.num_items);
+ std::copy_n(other.items, other.num_items, std::back_inserter(items));
+
+ /* discard the pointers from the Tag object */
+ other.num_items = 0;
+ delete[] other.items;
+ other.items = nullptr;
+
+ return *this;
+}
+
+void
+TagBuilder::Clear()
+{
+ duration = SignedSongTime::Negative();
+ has_playlist = false;
+ RemoveAll();
}
void
@@ -48,7 +116,7 @@ TagBuilder::Commit(Tag &tag)
{
tag.Clear();
- tag.time = time;
+ tag.duration = duration;
tag.has_playlist = has_playlist;
/* move all TagItem pointers to the new Tag object without
@@ -57,7 +125,7 @@ TagBuilder::Commit(Tag &tag)
object */
const unsigned n_items = items.size();
tag.num_items = n_items;
- tag.items = g_new(TagItem *, n_items);
+ tag.items = new TagItem *[n_items];
std::copy_n(items.begin(), n_items, tag.items);
items.clear();
@@ -66,14 +134,51 @@ TagBuilder::Commit(Tag &tag)
Clear();
}
-Tag *
+Tag
TagBuilder::Commit()
{
+ Tag tag;
+ Commit(tag);
+ return tag;
+}
+
+Tag *
+TagBuilder::CommitNew()
+{
Tag *tag = new Tag();
Commit(*tag);
return tag;
}
+bool
+TagBuilder::HasType(TagType type) const
+{
+ for (auto i : items)
+ if (i->type == type)
+ return true;
+
+ return false;
+}
+
+void
+TagBuilder::Complement(const Tag &other)
+{
+ if (duration.IsNegative())
+ duration = other.duration;
+
+ has_playlist |= other.has_playlist;
+
+ items.reserve(items.size() + other.num_items);
+
+ tag_pool_lock.lock();
+ for (unsigned i = 0, n = other.num_items; i != n; ++i) {
+ TagItem *item = other.items[i];
+ if (!HasType(item->type))
+ items.push_back(tag_pool_dup_item(item));
+ }
+ tag_pool_lock.unlock();
+}
+
inline void
TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
{
@@ -83,17 +188,17 @@ TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
#endif
assert(length > 0);
- char *p = FixTagString(value, length);
- if (p != nullptr) {
- value = p;
- length = strlen(value);
+ auto f = FixTagString(value, length);
+ if (!f.IsNull()) {
+ value = f.data;
+ length = f.size;
}
tag_pool_lock.lock();
auto i = tag_pool_get_item(type, value, length);
tag_pool_lock.unlock();
- g_free(p);
+ free(f.data);
items.push_back(i);
}
@@ -122,3 +227,39 @@ TagBuilder::AddItem(TagType type, const char *value)
AddItem(type, value, strlen(value));
}
+
+void
+TagBuilder::AddEmptyItem(TagType type)
+{
+ tag_pool_lock.lock();
+ auto i = tag_pool_get_item(type, "", 0);
+ tag_pool_lock.unlock();
+
+ items.push_back(i);
+}
+
+void
+TagBuilder::RemoveAll()
+{
+ tag_pool_lock.lock();
+ for (auto i : items)
+ tag_pool_put_item(i);
+ tag_pool_lock.unlock();
+
+ items.clear();
+}
+
+void
+TagBuilder::RemoveType(TagType type)
+{
+ const auto begin = items.begin(), end = items.end();
+
+ items.erase(std::remove_if(begin, end,
+ [type](TagItem *item) {
+ if (item->type != type)
+ return false;
+ tag_pool_put_item(item);
+ return true;
+ }),
+ end);
+}
diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx
index ffc60a1b2..aff581313 100644
--- a/src/tag/TagBuilder.hxx
+++ b/src/tag/TagBuilder.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,7 @@
#define MPD_TAG_BUILDER_HXX
#include "TagType.h"
+#include "Chrono.hxx"
#include "Compiler.h"
#include <vector>
@@ -35,12 +36,10 @@ struct Tag;
*/
class TagBuilder {
/**
- * 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.
+ * The duration of the song. A negative value means that the
+ * length is unknown.
*/
- int time;
+ SignedSongTime duration;
/**
* Does this file have an embedded playlist (e.g. embedded CUE
@@ -56,18 +55,25 @@ public:
* Create an empty tag.
*/
TagBuilder()
- :time(-1), has_playlist(false) {}
+ :duration(SignedSongTime::Negative()), has_playlist(false) {}
~TagBuilder() {
Clear();
}
TagBuilder(const TagBuilder &other) = delete;
- TagBuilder &operator=(const TagBuilder &other) = delete;
+
+ explicit TagBuilder(const Tag &other);
+ explicit TagBuilder(Tag &&other);
+
+ TagBuilder &operator=(const TagBuilder &other);
+ TagBuilder &operator=(TagBuilder &&other);
+
+ TagBuilder &operator=(Tag &&other);
/**
- * Returns true if the tag contains no items. This ignores the "time"
- * attribute.
+ * Returns true if the tag contains no items. This ignores
+ * the "duration" attribute.
*/
bool IsEmpty() const {
return items.empty();
@@ -78,7 +84,7 @@ public:
*/
gcc_pure
bool IsDefined() const {
- return time >= 0 || has_playlist || !IsEmpty();
+ return !duration.IsNegative() || has_playlist || !IsEmpty();
}
void Clear();
@@ -90,14 +96,20 @@ public:
void Commit(Tag &tag);
/**
+ * Create a new #Tag instance from data in this object. This
+ * object is empty afterwards.
+ */
+ Tag Commit();
+
+ /**
* Create a new #Tag instance from data in this object. The
* returned object is owned by the caller. This object is
* empty afterwards.
*/
- Tag *Commit();
+ Tag *CommitNew();
- void SetTime(int _time) {
- time = _time;
+ void SetDuration(SignedSongTime _duration) {
+ duration = _duration;
}
void SetHasPlaylist(bool _has_playlist) {
@@ -109,6 +121,19 @@ public:
}
/**
+ * Checks whether the tag contains one or more items with
+ * the specified type.
+ */
+ gcc_pure
+ bool HasType(TagType type) const;
+
+ /**
+ * Copy attributes and items from the other object that do not
+ * exist in this object.
+ */
+ void Complement(const Tag &other);
+
+ /**
* Appends a new tag item.
*
* @param type the type of the new tag item
@@ -127,6 +152,23 @@ public:
gcc_nonnull_all
void AddItem(TagType type, const char *value);
+ /**
+ * Appends a new tag item with an empty value. Do not use
+ * this unless you know what you're doing - because usually,
+ * empty values are discarded.
+ */
+ void AddEmptyItem(TagType type);
+
+ /**
+ * Removes all tag items.
+ */
+ void RemoveAll();
+
+ /**
+ * Removes all tag items of the specified type.
+ */
+ void RemoveType(TagType type);
+
private:
gcc_nonnull_all
void AddItemInternal(TagType type, const char *value, size_t length);
diff --git a/src/tag/TagConfig.cxx b/src/tag/TagConfig.cxx
index b8be4fc4c..00f20d1c0 100644
--- a/src/tag/TagConfig.cxx
+++ b/src/tag/TagConfig.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,16 +21,16 @@
#include "TagConfig.hxx"
#include "TagSettings.h"
#include "Tag.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
#include "system/FatalError.hxx"
+#include "util/Alloc.hxx"
#include "util/ASCII.hxx"
-
-#include <glib.h>
+#include "util/StringUtil.hxx"
#include <algorithm>
-#include <string.h>
+#include <stdlib.h>
void
TagLoadConfig()
@@ -46,14 +46,14 @@ TagLoadConfig()
bool quit = false;
char *temp, *c, *s;
- temp = c = s = g_strdup(value);
+ temp = c = s = xstrdup(value);
do {
if (*s == ',' || *s == '\0') {
if (*s == '\0')
quit = true;
*s = '\0';
- c = g_strstrip(c);
+ c = Strip(c);
if (*c == 0)
continue;
@@ -70,5 +70,5 @@ TagLoadConfig()
s++;
} while (!quit);
- g_free(temp);
+ free(temp);
}
diff --git a/src/tag/TagConfig.hxx b/src/tag/TagConfig.hxx
index 5ec6766d4..0088e9757 100644
--- a/src/tag/TagConfig.hxx
+++ b/src/tag/TagConfig.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,8 +20,6 @@
#ifndef MPD_TAG_CONFIG_HXX
#define MPD_TAG_CONFIG_HXX
-#include "TagType.h"
-
void
TagLoadConfig();
diff --git a/src/tag/TagHandler.cxx b/src/tag/TagHandler.cxx
index 80982ef50..2cbb83242 100644
--- a/src/tag/TagHandler.cxx
+++ b/src/tag/TagHandler.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -23,11 +23,11 @@
#include "util/ASCII.hxx"
static void
-add_tag_duration(unsigned seconds, void *ctx)
+add_tag_duration(SongTime duration, void *ctx)
{
TagBuilder &tag = *(TagBuilder *)ctx;
- tag.SetTime(seconds);
+ tag.SetDuration(duration);
}
static void
diff --git a/src/tag/TagHandler.hxx b/src/tag/TagHandler.hxx
index b8c3c6b79..c12b605bc 100644
--- a/src/tag/TagHandler.hxx
+++ b/src/tag/TagHandler.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@
#include "check.h"
#include "TagType.h"
+#include "Chrono.hxx"
#include <assert.h>
@@ -30,11 +31,11 @@
*/
struct tag_handler {
/**
- * Declare the duration of a song, in seconds. Do not call
+ * Declare the duration of a song. 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);
+ void (*duration)(SongTime duration, void *ctx);
/**
* A tag has been read.
@@ -53,12 +54,12 @@ struct tag_handler {
static inline void
tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx,
- unsigned seconds)
+ SongTime duration)
{
assert(handler != nullptr);
if (handler->duration != nullptr)
- handler->duration(seconds, ctx);
+ handler->duration(duration, ctx);
}
static inline void
diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx
index df70a95e5..02dc58364 100644
--- a/src/tag/TagId3.cxx
+++ b/src/tag/TagId3.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,23 +21,28 @@
#include "TagId3.hxx"
#include "TagHandler.hxx"
#include "TagTable.hxx"
-#include "Tag.hxx"
#include "TagBuilder.hxx"
+#include "util/Alloc.hxx"
+#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include "ConfigGlobal.hxx"
+#include "config/ConfigGlobal.hxx"
#include "Riff.hxx"
#include "Aiff.hxx"
#include "fs/Path.hxx"
#include "fs/FileSystem.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
+
#include <id3tag.h>
+#include <string>
+
#include <stdio.h>
#include <stdlib.h>
-#include <errno.h>
#include <string.h>
# ifndef ID3_FRAME_COMPOSER
@@ -70,14 +75,11 @@ tag_is_id3v1(struct id3_tag *tag)
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
- union id3_field *field;
- const id3_ucs4_t *ucs4;
-
- field = id3_frame_field(frame, i);
+ id3_field *field = id3_frame_field(frame, i);
if (field == nullptr)
return nullptr;
- ucs4 = id3_field_getstring(field);
+ const id3_ucs4_t *ucs4 = id3_field_getstring(field);
if (ucs4 == nullptr)
return nullptr;
@@ -89,17 +91,16 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i)
static id3_utf8_t *
import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
{
- id3_utf8_t *utf8, *utf8_stripped;
- id3_latin1_t *isostr;
- const char *encoding;
+ id3_utf8_t *utf8;
+#ifdef HAVE_GLIB
/* use encoding field here? */
+ const char *encoding;
if (is_id3v1 &&
(encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) {
- isostr = id3_ucs4_latin1duplicate(ucs4);
- if (G_UNLIKELY(!isostr)) {
+ id3_latin1_t *isostr = id3_ucs4_latin1duplicate(ucs4);
+ if (gcc_unlikely(isostr == nullptr))
return nullptr;
- }
utf8 = (id3_utf8_t *)
g_convert_with_fallback((const char*)isostr, -1,
@@ -110,19 +111,24 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
FormatWarning(id3_domain,
"Unable to convert %s string to UTF-8: '%s'",
encoding, isostr);
- g_free(isostr);
+ free(isostr);
return nullptr;
}
- g_free(isostr);
+ free(isostr);
} else {
+#else
+ (void)is_id3v1;
+#endif
utf8 = id3_ucs4_utf8duplicate(ucs4);
- if (G_UNLIKELY(!utf8)) {
+ if (gcc_unlikely(utf8 == nullptr))
return nullptr;
- }
+#ifdef HAVE_GLIB
}
+#endif
- utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8));
- g_free(utf8);
+ id3_utf8_t *utf8_stripped = (id3_utf8_t *)
+ xstrdup(Strip((char *)utf8));
+ free(utf8);
return utf8_stripped;
}
@@ -139,17 +145,12 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
TagType type,
const struct tag_handler *handler, void *handler_ctx)
{
- id3_ucs4_t const *ucs4;
- id3_utf8_t *utf8;
- union id3_field const *field;
- unsigned int nstrings, i;
-
if (frame->nfields != 2)
return;
/* check the encoding field */
- field = id3_frame_field(frame, 0);
+ const id3_field *field = id3_frame_field(frame, 0);
if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
return;
@@ -160,22 +161,22 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
return;
/* Get the number of strings available */
- nstrings = id3_field_getnstrings(field);
- for (i = 0; i < nstrings; i++) {
- ucs4 = id3_field_getstrings(field, i);
+ const unsigned nstrings = id3_field_getnstrings(field);
+ for (unsigned i = 0; i < nstrings; i++) {
+ const id3_ucs4_t *ucs4 = id3_field_getstrings(field, i);
if (ucs4 == nullptr)
continue;
if (type == TAG_GENRE)
ucs4 = id3_genre_name(ucs4);
- utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
if (utf8 == nullptr)
continue;
tag_handler_invoke_tag(handler, handler_ctx,
type, (const char *)utf8);
- g_free(utf8);
+ free(utf8);
}
}
@@ -209,28 +210,24 @@ tag_id3_import_comment_frame(struct id3_tag *tag,
const struct tag_handler *handler,
void *handler_ctx)
{
- id3_ucs4_t const *ucs4;
- id3_utf8_t *utf8;
- union id3_field const *field;
-
if (frame->nfields != 4)
return;
/* for now I only read the 4th field, with the fullstring */
- field = id3_frame_field(frame, 3);
+ const id3_field *field = id3_frame_field(frame, 3);
if (field == nullptr)
return;
- ucs4 = id3_field_getfullstring(field);
+ const id3_ucs4_t *ucs4 = id3_field_getfullstring(field);
if (ucs4 == nullptr)
return;
- utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
if (utf8 == nullptr)
return;
tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
- g_free(utf8);
+ free(utf8);
}
/**
@@ -262,6 +259,8 @@ tag_id3_parse_txxx_name(const char *name)
{ "MusicBrainz Album Artist Id",
TAG_MUSICBRAINZ_ALBUMARTISTID },
{ "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID },
+ { "MusicBrainz Release Track Id",
+ TAG_MUSICBRAINZ_RELEASETRACKID },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};
@@ -277,19 +276,15 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
void *handler_ctx)
{
for (unsigned i = 0;; ++i) {
- const struct id3_frame *frame;
- id3_utf8_t *name, *value;
- TagType type;
-
- frame = id3_tag_findframe(id3_tag, "TXXX", i);
+ const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i);
if (frame == nullptr)
break;
- name = tag_id3_getstring(frame, 1);
+ id3_utf8_t *name = tag_id3_getstring(frame, 1);
if (name == nullptr)
continue;
- value = tag_id3_getstring(frame, 2);
+ id3_utf8_t *value = tag_id3_getstring(frame, 2);
if (value == nullptr)
continue;
@@ -297,7 +292,7 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
(const char *)name,
(const char *)value);
- type = tag_id3_parse_txxx_name((const char*)name);
+ TagType type = tag_id3_parse_txxx_name((const char*)name);
free(name);
if (type != TAG_NUM_OF_ITEM_TYPES)
@@ -316,21 +311,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag,
const struct tag_handler *handler, void *handler_ctx)
{
for (unsigned i = 0;; ++i) {
- const struct id3_frame *frame;
- union id3_field *field;
- const id3_latin1_t *name;
- const id3_byte_t *value;
- id3_length_t length;
-
- frame = id3_tag_findframe(id3_tag, "UFID", i);
+ const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i);
if (frame == nullptr)
break;
- field = id3_frame_field(frame, 0);
+ id3_field *field = id3_frame_field(frame, 0);
if (field == nullptr)
continue;
- name = id3_field_getlatin1(field);
+ const id3_latin1_t *name = id3_field_getlatin1(field);
if (name == nullptr ||
strcmp((const char *)name, "http://musicbrainz.org") != 0)
continue;
@@ -339,14 +328,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag,
if (field == nullptr)
continue;
- value = id3_field_getbinarydata(field, &length);
+ id3_length_t length;
+ const id3_byte_t *value =
+ id3_field_getbinarydata(field, &length);
if (value == nullptr || length == 0)
continue;
- char *p = g_strndup((const char *)value, length);
+ std::string p((const char *)value, length);
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_MUSICBRAINZ_TRACKID, p);
- g_free(p);
+ TAG_MUSICBRAINZ_TRACKID, p.c_str());
}
}
@@ -360,6 +350,9 @@ scan_id3_tag(struct id3_tag *tag,
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, "TSOA", TAG_ALBUM_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,
@@ -393,73 +386,57 @@ tag_id3_import(struct id3_tag *tag)
scan_id3_tag(tag, &add_tag_handler, &tag_builder);
return tag_builder.IsEmpty()
? nullptr
- : tag_builder.Commit();
+ : tag_builder.CommitNew();
}
-static int
+static size_t
fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence)
{
if (fseek(stream, offset, whence) != 0) return 0;
return fread(buf, 1, size, stream);
}
-static int
+static long
get_id3v2_footer_size(FILE *stream, long offset, int whence)
{
id3_byte_t buf[ID3_TAG_QUERYSIZE];
- int bufsize;
-
- bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
- if (bufsize <= 0) return 0;
+ size_t bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
+ if (bufsize == 0) return 0;
return id3_tag_query(buf, bufsize);
}
static struct id3_tag *
tag_id3_read(FILE *stream, long offset, int whence)
{
- struct id3_tag *tag;
- id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
- int tag_size;
- int query_buffer_size;
-
/* It's ok if we get less than we asked for */
- query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
- stream, offset, whence);
+ id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
+ size_t query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
+ stream, offset, whence);
if (query_buffer_size <= 0)
return nullptr;
/* Look for a tag header */
- tag_size = id3_tag_query(query_buffer, query_buffer_size);
+ long tag_size = id3_tag_query(query_buffer, query_buffer_size);
if (tag_size <= 0) return nullptr;
/* Found a tag. Allocate a buffer and read it in. */
- id3_byte_t *tag_buffer = (id3_byte_t *)g_malloc(tag_size);
- if (!tag_buffer)
- return nullptr;
-
+ id3_byte_t *tag_buffer = new id3_byte_t[tag_size];
int tag_buffer_size = fill_buffer(tag_buffer, tag_size,
stream, offset, whence);
if (tag_buffer_size < tag_size) {
- g_free(tag_buffer);
+ delete[] tag_buffer;
return nullptr;
}
- tag = id3_tag_parse(tag_buffer, tag_buffer_size);
-
- g_free(tag_buffer);
-
+ id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size);
+ delete[] tag_buffer;
return tag;
}
static struct id3_tag *
tag_id3_find_from_beginning(FILE *stream)
{
- struct id3_tag *tag;
- struct id3_tag *seektag;
- struct id3_frame *frame;
- int seek;
-
- tag = tag_id3_read(stream, 0, SEEK_SET);
+ id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET);
if (!tag) {
return nullptr;
} else if (tag_is_id3v1(tag)) {
@@ -469,14 +446,15 @@ tag_id3_find_from_beginning(FILE *stream)
}
/* We have an id3v2 tag, so let's look for SEEK frames */
+ id3_frame *frame;
while ((frame = id3_tag_findframe(tag, "SEEK", 0))) {
/* Found a SEEK frame, get it's value */
- seek = id3_field_getint(id3_frame_field(frame, 0));
+ int seek = id3_field_getint(id3_frame_field(frame, 0));
if (seek < 0)
break;
/* Get the tag specified by the SEEK frame */
- seektag = tag_id3_read(stream, seek, SEEK_CUR);
+ id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR);
if (!seektag || tag_is_id3v1(seektag))
break;
@@ -491,20 +469,16 @@ tag_id3_find_from_beginning(FILE *stream)
static struct id3_tag *
tag_id3_find_from_end(FILE *stream)
{
- struct id3_tag *tag;
- struct id3_tag *v1tag;
- int tagsize;
-
/* Get an id3v1 tag from the end of file for later use */
- v1tag = tag_id3_read(stream, -128, SEEK_END);
+ id3_tag *v1tag = tag_id3_read(stream, -128, SEEK_END);
/* Get the id3v2 tag size from the footer (located before v1tag) */
- tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
+ int tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
if (tagsize >= 0)
return v1tag;
/* Get the tag which the footer belongs to */
- tag = tag_id3_read(stream, tagsize, SEEK_CUR);
+ id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR);
if (!tag)
return v1tag;
@@ -527,16 +501,16 @@ tag_id3_riff_aiff_load(FILE *file)
/* too large, don't allocate so much memory */
return nullptr;
- id3_byte_t *buffer = (id3_byte_t *)g_malloc(size);
+ id3_byte_t *buffer = new id3_byte_t[size];
size_t ret = fread(buffer, size, 1, file);
if (ret != 1) {
LogWarning(id3_domain, "Failed to read RIFF chunk");
- g_free(buffer);
+ delete[] buffer;
return nullptr;
}
struct id3_tag *tag = id3_tag_parse(buffer, size);
- g_free(buffer);
+ delete[] buffer;
return tag;
}
@@ -545,7 +519,7 @@ tag_id3_load(Path path_fs, Error &error)
{
FILE *file = FOpen(path_fs, "rb");
if (file == nullptr) {
- error.FormatErrno("Failed to open file %s", path_fs);
+ error.FormatErrno("Failed to open file %s", path_fs.c_str());
return nullptr;
}
diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx
index 749166116..1928d539d 100644
--- a/src/tag/TagId3.hxx
+++ b/src/tag/TagId3.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -33,10 +33,10 @@ class Error;
bool
tag_id3_scan(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
Tag *
-tag_id3_import(struct id3_tag *);
+tag_id3_import(id3_tag *);
/**
* Loads the ID3 tags from the file into a libid3tag object. The
@@ -53,8 +53,8 @@ tag_id3_load(Path path_fs, Error &error);
*
*/
void
-scan_id3_tag(struct id3_tag *tag,
- const struct tag_handler *handler, void *handler_ctx);
+scan_id3_tag(id3_tag *tag,
+ const tag_handler *handler, void *handler_ctx);
#else
@@ -62,7 +62,7 @@ scan_id3_tag(struct id3_tag *tag,
static inline bool
tag_id3_scan(gcc_unused Path path_fs,
- gcc_unused const struct tag_handler *handler,
+ gcc_unused const tag_handler *handler,
gcc_unused void *handler_ctx)
{
return false;
diff --git a/src/tag/TagItem.hxx b/src/tag/TagItem.hxx
index 7c3100393..489ecde3a 100644
--- a/src/tag/TagItem.hxx
+++ b/src/tag/TagItem.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/TagNames.c b/src/tag/TagNames.c
index eac6fc59a..e051c5863 100644
--- a/src/tag/TagNames.c
+++ b/src/tag/TagNames.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,6 +24,7 @@ const char *const tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
[TAG_ARTIST] = "Artist",
[TAG_ARTIST_SORT] = "ArtistSort",
[TAG_ALBUM] = "Album",
+ [TAG_ALBUM_SORT] = "AlbumSort",
[TAG_ALBUM_ARTIST] = "AlbumArtist",
[TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
[TAG_TITLE] = "Title",
@@ -41,4 +42,5 @@ const char *const tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
[TAG_MUSICBRAINZ_ALBUMID] = "MUSICBRAINZ_ALBUMID",
[TAG_MUSICBRAINZ_ALBUMARTISTID] = "MUSICBRAINZ_ALBUMARTISTID",
[TAG_MUSICBRAINZ_TRACKID] = "MUSICBRAINZ_TRACKID",
+ [TAG_MUSICBRAINZ_RELEASETRACKID] = "MUSICBRAINZ_RELEASETRACKID",
};
diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx
index cc28ea9a6..29f605337 100644
--- a/src/tag/TagPool.cxx
+++ b/src/tag/TagPool.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,23 +20,46 @@
#include "config.h"
#include "TagPool.hxx"
#include "TagItem.hxx"
-
-#include <glib.h>
+#include "util/Cast.hxx"
+#include "util/VarSize.hxx"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
Mutex tag_pool_lock;
-#define NUM_SLOTS 4096
+static constexpr size_t NUM_SLOTS = 4096;
-struct slot {
- struct slot *next;
+struct TagPoolSlot {
+ TagPoolSlot *next;
unsigned char ref;
TagItem item;
-} mpd_packed;
-static struct slot *slots[NUM_SLOTS];
+ TagPoolSlot(TagPoolSlot *_next, TagType type,
+ const char *value, size_t length)
+ :next(_next), ref(1) {
+ item.type = type;
+ memcpy(item.value, value, length);
+ item.value[length] = 0;
+ }
+
+ static TagPoolSlot *Create(TagPoolSlot *_next, TagType type,
+ const char *value, size_t length);
+} gcc_packed;
+
+TagPoolSlot *
+TagPoolSlot::Create(TagPoolSlot *_next, TagType type,
+ const char *value, size_t length)
+{
+ TagPoolSlot *dummy;
+ return NewVarSize<TagPoolSlot>(sizeof(dummy->item.value),
+ length + 1,
+ _next, type,
+ value, length);
+}
+
+static TagPoolSlot *slots[NUM_SLOTS];
static inline unsigned
calc_hash_n(TagType type, const char *p, size_t length)
@@ -64,35 +87,32 @@ calc_hash(TagType type, const char *p)
return hash ^ type;
}
-static inline struct slot *
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+ constexpr
+#endif
+static inline TagPoolSlot *
tag_item_to_slot(TagItem *item)
{
- return (struct slot*)(((char*)item) - offsetof(struct slot, item));
+ return &ContainerCast(*item, &TagPoolSlot::item);
}
-static struct slot *slot_alloc(struct slot *next,
- TagType type,
- const char *value, int length)
+static inline TagPoolSlot **
+tag_value_slot_p(TagType type, const char *value, size_t length)
{
- struct slot *slot;
-
- slot = (struct slot *)
- g_malloc(sizeof(*slot) - sizeof(slot->item.value) + length + 1);
- slot->next = next;
- slot->ref = 1;
- slot->item.type = type;
- memcpy(slot->item.value, value, length);
- slot->item.value[length] = 0;
- return slot;
+ return &slots[calc_hash_n(type, value, length) % NUM_SLOTS];
+}
+
+static inline TagPoolSlot **
+tag_value_slot_p(TagType type, const char *value)
+{
+ return &slots[calc_hash(type, value) % NUM_SLOTS];
}
TagItem *
tag_pool_get_item(TagType 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) {
+ auto slot_p = tag_value_slot_p(type, value, length);
+ for (auto 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 &&
@@ -103,7 +123,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
}
}
- slot = slot_alloc(*slot_p, type, value, length);
+ auto slot = TagPoolSlot::Create(*slot_p, type, value, length);
*slot_p = slot;
return &slot->item;
}
@@ -111,7 +131,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
TagItem *
tag_pool_dup_item(TagItem *item)
{
- struct slot *slot = tag_item_to_slot(item);
+ TagPoolSlot *slot = tag_item_to_slot(item);
assert(slot->ref > 0);
@@ -122,11 +142,10 @@ tag_pool_dup_item(TagItem *item)
/* the reference counter overflows above 0xff;
duplicate the item, and start with 1 */
size_t length = strlen(item->value);
- struct slot **slot_p =
- &slots[calc_hash_n(item->type, item->value,
- length) % NUM_SLOTS];
- slot = slot_alloc(*slot_p, item->type,
- item->value, strlen(item->value));
+ auto slot_p = tag_value_slot_p(item->type,
+ item->value, length);
+ slot = TagPoolSlot::Create(*slot_p, item->type,
+ item->value, strlen(item->value));
*slot_p = slot;
return &slot->item;
}
@@ -135,7 +154,7 @@ tag_pool_dup_item(TagItem *item)
void
tag_pool_put_item(TagItem *item)
{
- struct slot **slot_p, *slot;
+ TagPoolSlot **slot_p, *slot;
slot = tag_item_to_slot(item);
assert(slot->ref > 0);
@@ -144,12 +163,12 @@ tag_pool_put_item(TagItem *item)
if (slot->ref > 0)
return;
- for (slot_p = &slots[calc_hash(item->type, item->value) % NUM_SLOTS];
+ for (slot_p = tag_value_slot_p(item->type, item->value);
*slot_p != slot;
slot_p = &(*slot_p)->next) {
assert(*slot_p != nullptr);
}
*slot_p = slot->next;
- g_free(slot);
+ DeleteVarSize(slot);
}
diff --git a/src/tag/TagPool.hxx b/src/tag/TagPool.hxx
index f41f3a724..990ee87bd 100644
--- a/src/tag/TagPool.hxx
+++ b/src/tag/TagPool.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/TagRva2.cxx b/src/tag/TagRva2.cxx
index 204001aa7..bbb6d11e6 100644
--- a/src/tag/TagRva2.cxx
+++ b/src/tag/TagRva2.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,10 +21,10 @@
#include "TagRva2.hxx"
#include "ReplayGainInfo.hxx"
+#include <id3tag.h>
+
#include <stdint.h>
#include <string.h>
-#include <glib.h>
-#include <id3tag.h>
enum rva2_channel {
CHANNEL_OTHER = 0x00,
diff --git a/src/tag/TagRva2.hxx b/src/tag/TagRva2.hxx
index 98154041a..df559f472 100644
--- a/src/tag/TagRva2.hxx
+++ b/src/tag/TagRva2.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -32,6 +32,6 @@ struct ReplayGainInfo;
* @return true on success
*/
bool
-tag_rva2_parse(struct id3_tag *tag, ReplayGainInfo &replay_gain_info);
+tag_rva2_parse(id3_tag *tag, ReplayGainInfo &replay_gain_info);
#endif
diff --git a/src/tag/TagSettings.c b/src/tag/TagSettings.c
index eb2db2ba5..e0c577c2b 100644
--- a/src/tag/TagSettings.c
+++ b/src/tag/TagSettings.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/TagSettings.h b/src/tag/TagSettings.h
index 245de2df5..33f89d4be 100644
--- a/src/tag/TagSettings.h
+++ b/src/tag/TagSettings.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/tag/TagString.cxx b/src/tag/TagString.cxx
index 9ab095249..4f07cd62a 100644
--- a/src/tag/TagString.cxx
+++ b/src/tag/TagString.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,52 +19,69 @@
#include "config.h"
#include "TagString.hxx"
-
-#include <glib.h>
+#include "util/Alloc.hxx"
+#include "util/WritableBuffer.hxx"
+#include "util/UTF8.hxx"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+
+gcc_pure
+static const char *
+FindInvalidUTF8(const char *p, const char *const end)
+{
+ while (p < end) {
+ const size_t s = SequenceLengthUTF8(*p);
+ if (p + s > end)
+ /* partial sequence at end of string */
+ return p;
+
+ /* now call the other SequenceLengthUTF8() overload
+ which also validates the continuations */
+ const size_t t = SequenceLengthUTF8(p);
+ assert(s == t);
+ if (t == 0)
+ return p;
+
+ p += s;
+ }
+
+ return nullptr;
+}
/**
* Replace invalid sequences with the question mark.
*/
-static char *
-patch_utf8(const char *src, size_t length, const gchar *end)
+static WritableBuffer<char>
+patch_utf8(const char *src, size_t length, const char *_invalid)
{
/* duplicate the string, and replace invalid bytes in that
buffer */
- char *dest = g_strndup(src, length);
+ char *dest = (char *)xmemdup(src, length);
+ char *const end = dest + length;
+ char *invalid = dest + (_invalid - src);
do {
- dest[end - src] = '?';
- } while (!g_utf8_validate(end + 1, (src + length) - (end + 1), &end));
+ *invalid = '?';
+
+ const char *__invalid = FindInvalidUTF8(invalid + 1, end);
+ invalid = const_cast<char *>(__invalid);
+ } while (invalid != nullptr);
- return dest;
+ return { dest, length };
}
-static char *
+static WritableBuffer<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))
+ const char *invalid = FindInvalidUTF8(str, str + length);
+ if (invalid == nullptr)
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);
+ /* no, broken - patch invalid sequences */
+ return patch_utf8(str, length, invalid);
}
static bool
@@ -87,40 +104,36 @@ find_non_printable(const char *p, size_t length)
* Clears all non-printable characters, convert them to space.
* Returns nullptr if nothing needs to be cleared.
*/
-static char *
+static WritableBuffer<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);
+ char *dest = (char *)xmemdup(p, length);
for (size_t i = first - p; i < length; ++i)
if (char_is_non_printable(dest[i]))
dest[i] = ' ';
- return dest;
+ return { dest, length };
}
-char *
+WritableBuffer<char>
FixTagString(const char *p, size_t length)
{
- char *utf8, *cleared;
-
- utf8 = fix_utf8(p, length);
- if (utf8 != nullptr) {
- p = utf8;
- length = strlen(p);
+ auto utf8 = fix_utf8(p, length);
+ if (!utf8.IsNull()) {
+ p = utf8.data;
+ length = utf8.size;
}
- cleared = clear_non_printable(p, length);
- if (cleared == nullptr)
+ WritableBuffer<char> cleared = clear_non_printable(p, length);
+ if (cleared.IsNull())
cleared = utf8;
else
- g_free(utf8);
+ free(utf8.data);
return cleared;
}
diff --git a/src/tag/TagString.hxx b/src/tag/TagString.hxx
index 79255dcd3..eccc2aa47 100644
--- a/src/tag/TagString.hxx
+++ b/src/tag/TagString.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,8 +25,10 @@
#include <stddef.h>
-gcc_malloc gcc_nonnull_all
-char *
+template<typename T> struct WritableBuffer;
+
+gcc_nonnull_all
+WritableBuffer<char>
FixTagString(const char *p, size_t length);
#endif
diff --git a/src/tag/TagTable.cxx b/src/tag/TagTable.cxx
index b51bc35ba..c6e1cff54 100644
--- a/src/tag/TagTable.cxx
+++ b/src/tag/TagTable.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -51,3 +51,13 @@ tag_table_lookup_i(const struct tag_table *table, const char *name)
return TAG_NUM_OF_ITEM_TYPES;
}
+
+const char *
+tag_table_lookup(const tag_table *table, TagType type)
+{
+ for (; table->name != nullptr; ++table)
+ if (table->type == type)
+ return table->name;
+
+ return nullptr;
+}
diff --git a/src/tag/TagTable.hxx b/src/tag/TagTable.hxx
index c050f61ac..095b4cbff 100644
--- a/src/tag/TagTable.hxx
+++ b/src/tag/TagTable.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -47,4 +47,13 @@ gcc_pure
TagType
tag_table_lookup_i(const tag_table *table, const char *name);
+/**
+ * Looks up a #TagType in a tag translation table and returns its
+ * string representation. Returns nullptr if the specified type was
+ * not found in the table.
+ */
+gcc_pure
+const char *
+tag_table_lookup(const tag_table *table, TagType type);
+
#endif
diff --git a/src/tag/TagType.h b/src/tag/TagType.h
index 0b8aa55d4..0aa6b4a51 100644
--- a/src/tag/TagType.h
+++ b/src/tag/TagType.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -38,6 +38,7 @@ enum TagType
TAG_ARTIST,
TAG_ARTIST_SORT,
TAG_ALBUM,
+ TAG_ALBUM_SORT,
TAG_ALBUM_ARTIST,
TAG_ALBUM_ARTIST_SORT,
TAG_TITLE,
@@ -54,6 +55,7 @@ enum TagType
TAG_MUSICBRAINZ_ALBUMID,
TAG_MUSICBRAINZ_ALBUMARTISTID,
TAG_MUSICBRAINZ_TRACKID,
+ TAG_MUSICBRAINZ_RELEASETRACKID,
TAG_NUM_OF_ITEM_TYPES
};
diff --git a/src/tag/VorbisComment.cxx b/src/tag/VorbisComment.cxx
new file mode 100644
index 000000000..2dfc058d8
--- /dev/null
+++ b/src/tag/VorbisComment.cxx
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VorbisComment.hxx"
+#include "util/ASCII.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+const char *
+vorbis_comment_value(const char *entry, const char *name)
+{
+ assert(entry != nullptr);
+ assert(name != nullptr);
+ assert(*name != 0);
+
+ const size_t length = strlen(name);
+
+ if (StringEqualsCaseASCII(entry, name, length) &&
+ entry[length] == '=')
+ return entry + length + 1;
+
+ return nullptr;
+}
diff --git a/src/tag/VorbisComment.hxx b/src/tag/VorbisComment.hxx
new file mode 100644
index 000000000..1dd3371c8
--- /dev/null
+++ b/src/tag/VorbisComment.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_VORBIS_COMMENT_HXX
+#define MPD_TAG_VORBIS_COMMENT_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+/**
+ * Checks if the specified name matches the entry's name, and if yes,
+ * returns the comment value.
+ */
+gcc_pure
+const char *
+vorbis_comment_value(const char *entry, const char *name);
+
+#endif
diff --git a/src/thread/Cond.hxx b/src/thread/Cond.hxx
index ed663dc9d..a05d1c67d 100644
--- a/src/thread/Cond.hxx
+++ b/src/thread/Cond.hxx
@@ -1,24 +1,34 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2009-2014 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_THREAD_COND_HXX
-#define MPD_THREAD_COND_HXX
+#ifndef THREAD_COND_HXX
+#define THREAD_COND_HXX
#ifdef WIN32
diff --git a/src/thread/CriticalSection.hxx b/src/thread/CriticalSection.hxx
index 8bc05b8f5..bb25f6c47 100644
--- a/src/thread/CriticalSection.hxx
+++ b/src/thread/CriticalSection.hxx
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_THREAD_CRITICAL_SECTION_HXX
-#define MPD_THREAD_CRITICAL_SECTION_HXX
+#ifndef THREAD_CRITICAL_SECTION_HXX
+#define THREAD_CRITICAL_SECTION_HXX
#include <windows.h>
diff --git a/src/thread/GLibCond.hxx b/src/thread/GLibCond.hxx
deleted file mode 100644
index 9ab08e9fd..000000000
--- a/src/thread/GLibCond.hxx
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 2c666c1ea..000000000
--- a/src/thread/GLibMutex.hxx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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/Id.hxx b/src/thread/Id.hxx
index 2372a12f5..11be0a56b 100644
--- a/src/thread/Id.hxx
+++ b/src/thread/Id.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx
index 4ed48c972..c17538549 100644
--- a/src/thread/Mutex.hxx
+++ b/src/thread/Mutex.hxx
@@ -1,24 +1,34 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2009-2014 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_THREAD_MUTEX_HXX
-#define MPD_THREAD_MUTEX_HXX
+#ifndef THREAD_MUTEX_HXX
+#define THREAD_MUTEX_HXX
#ifdef WIN32
diff --git a/src/thread/Name.hxx b/src/thread/Name.hxx
new file mode 100644
index 000000000..284d1e147
--- /dev/null
+++ b/src/thread/Name.hxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_THREAD_NAME_HXX
+#define MPD_THREAD_NAME_HXX
+
+#ifdef HAVE_PTHREAD_SETNAME_NP
+#include <pthread.h>
+#include <stdio.h>
+#elif defined(HAVE_PRCTL)
+#include <sys/prctl.h>
+#endif
+
+static inline void
+SetThreadName(const char *name)
+{
+#ifdef HAVE_PTHREAD_SETNAME_NP
+#ifdef __APPLE__
+ pthread_setname_np(name);
+#else
+ pthread_setname_np(pthread_self(), name);
+#endif
+#elif defined(HAVE_PRCTL) && defined(PR_SET_NAME)
+ prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0);
+#else
+ (void)name;
+#endif
+}
+
+template<typename... Args>
+static inline void
+FormatThreadName(const char *fmt, gcc_unused Args&&... args)
+{
+#ifdef HAVE_PTHREAD_SETNAME_NP
+ char buffer[16];
+ snprintf(buffer, sizeof(buffer), fmt, args...);
+ SetThreadName(buffer);
+#else
+ (void)fmt;
+#endif
+}
+
+#endif
diff --git a/src/thread/PosixCond.hxx b/src/thread/PosixCond.hxx
index c2797649a..b3fe204e1 100644
--- a/src/thread/PosixCond.hxx
+++ b/src/thread/PosixCond.hxx
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_THREAD_POSIX_COND_HXX
-#define MPD_THREAD_POSIX_COND_HXX
+#ifndef THREAD_POSIX_COND_HXX
+#define THREAD_POSIX_COND_HXX
#include "PosixMutex.hxx"
@@ -41,7 +41,7 @@ class PosixCond {
pthread_cond_t cond;
public:
-#ifdef __NetBSD__
+#if defined(__NetBSD__) || defined(__BIONIC__)
/* NetBSD's PTHREAD_COND_INITIALIZER is not compatible with
"constexpr" */
PosixCond() {
diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx
index 445c0ace2..5805158d5 100644
--- a/src/thread/PosixMutex.hxx
+++ b/src/thread/PosixMutex.hxx
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_THREAD_POSIX_MUTEX_HXX
-#define MPD_THREAD_POSIX_MUTEX_HXX
+#ifndef THREAD_POSIX_MUTEX_HXX
+#define THREAD_POSIX_MUTEX_HXX
#include <pthread.h>
@@ -41,7 +41,7 @@ class PosixMutex {
pthread_mutex_t mutex;
public:
-#ifdef __NetBSD__
+#if defined(__NetBSD__) || defined(__BIONIC__)
/* NetBSD's PTHREAD_MUTEX_INITIALIZER is not compatible with
"constexpr" */
PosixMutex() {
diff --git a/src/thread/Slack.hxx b/src/thread/Slack.hxx
new file mode 100644
index 000000000..66b2254a4
--- /dev/null
+++ b/src/thread/Slack.hxx
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_THREAD_SLACK_HXX
+#define MPD_THREAD_SLACK_HXX
+
+#ifdef HAVE_PRCTL
+#include <sys/prctl.h>
+#endif
+
+/**
+ * Set the current thread's timer slack to the specified number of
+ * nanoseconds (requires Linux 2.6.28). This allows the kernel to
+ * merge multiple wakeups, which is a trick to save energy.
+ */
+static inline void
+SetThreadTimerSlackNS(unsigned long slack_ns)
+{
+#if defined(HAVE_PRCTL) && defined(PR_SET_TIMERSLACK)
+ prctl(PR_SET_TIMERSLACK, slack_ns, 0, 0, 0);
+#else
+ (void)slack_ns;
+#endif
+}
+
+static inline void
+SetThreadTimerSlackUS(unsigned long slack_us)
+{
+ SetThreadTimerSlackNS(slack_us * 1000ul);
+}
+
+static inline void
+SetThreadTimerSlackMS(unsigned long slack_ms)
+{
+ SetThreadTimerSlackNS(slack_ms * 1000000ul);
+}
+
+#endif
diff --git a/src/thread/Thread.cxx b/src/thread/Thread.cxx
index 67bcf7184..2932d478f 100644
--- a/src/thread/Thread.cxx
+++ b/src/thread/Thread.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,10 @@
#include "Thread.hxx"
#include "util/Error.hxx"
+#ifdef ANDROID
+#include "java/Global.hxx"
+#endif
+
bool
Thread::Start(void (*_f)(void *ctx), void *_ctx, Error &error)
{
@@ -102,6 +106,11 @@ Thread::ThreadProc(void *ctx)
#endif
thread.f(thread.ctx);
+
+#ifdef ANDROID
+ Java::DetachCurrentThread();
+#endif
+
return nullptr;
}
diff --git a/src/thread/Thread.hxx b/src/thread/Thread.hxx
index d3bd75455..976ff5625 100644
--- a/src/thread/Thread.hxx
+++ b/src/thread/Thread.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -68,7 +68,7 @@ public:
Thread(const Thread &) = delete;
#ifndef NDEBUG
- virtual ~Thread() {
+ ~Thread() {
/* all Thread objects must be destructed manually by calling
Join(), to clean up */
assert(!IsDefined());
diff --git a/src/thread/Util.hxx b/src/thread/Util.hxx
new file mode 100644
index 000000000..ff8dbbe10
--- /dev/null
+++ b/src/thread/Util.hxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef THREAD_UTIL_HXX
+#define THREAD_UTIL_HXX
+
+#ifdef __linux__
+#include <sched.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#elif defined(WIN32)
+#include <windows.h>
+#endif
+
+#ifdef __linux__
+
+static int
+ioprio_set(int which, int who, int ioprio)
+{
+ return syscall(__NR_ioprio_set, which, who, ioprio);
+}
+
+static void
+ioprio_set_idle()
+{
+ static constexpr int _IOPRIO_WHO_PROCESS = 1;
+ static constexpr int _IOPRIO_CLASS_IDLE = 3;
+ static constexpr int _IOPRIO_CLASS_SHIFT = 13;
+ static constexpr int _IOPRIO_IDLE =
+ (_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7;
+
+ ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
+}
+
+#endif
+
+/**
+ * Lower the current thread's priority to "idle" (very low).
+ */
+static inline void
+SetThreadIdlePriority()
+{
+#ifdef __linux__
+#ifdef SCHED_IDLE
+ static struct sched_param sched_param;
+ sched_setscheduler(0, SCHED_IDLE, &sched_param);
+#endif
+
+ ioprio_set_idle();
+
+#elif defined(WIN32)
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
+#endif
+};
+
+/**
+ * Raise the current thread's priority to "real-time" (very high).
+ */
+static inline void
+SetThreadRealtime()
+{
+#ifdef __linux__
+ struct sched_param sched_param;
+ sched_param.sched_priority = 50;
+
+ int policy = SCHED_FIFO;
+#ifdef SCHED_RESET_ON_FORK
+ policy |= SCHED_RESET_ON_FORK;
+#endif
+
+ sched_setscheduler(0, policy, &sched_param);
+#endif
+};
+
+#endif
diff --git a/src/thread/WindowsCond.hxx b/src/thread/WindowsCond.hxx
index c05bc05b2..2ce7271b9 100644
--- a/src/thread/WindowsCond.hxx
+++ b/src/thread/WindowsCond.hxx
@@ -27,8 +27,8 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_THREAD_WINDOWS_COND_HXX
-#define MPD_THREAD_WINDOWS_COND_HXX
+#ifndef THREAD_WINDOWS_COND_HXX
+#define THREAD_WINDOWS_COND_HXX
#include "CriticalSection.hxx"
diff --git a/src/unix/Daemon.cxx b/src/unix/Daemon.cxx
new file mode 100644
index 000000000..490b2def5
--- /dev/null
+++ b/src/unix/Daemon.cxx
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "Daemon.hxx"
+#include "system/FatalError.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Domain.hxx"
+#include "PidFile.hxx"
+#include "Log.hxx"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifndef WIN32
+#include <sys/wait.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#endif
+
+static constexpr Domain daemon_domain("daemon");
+
+#ifndef WIN32
+
+/** the Unix user name which MPD runs as */
+static char *user_name;
+
+/** the Unix user id which MPD runs as */
+static uid_t user_uid = (uid_t)-1;
+
+/** the Unix group id which MPD runs as */
+static gid_t user_gid = (gid_t)-1;
+
+/** the absolute path of the pidfile */
+static AllocatedPath pidfile = AllocatedPath::Null();
+
+/* whether "group" conf. option was given */
+static bool had_group = false;
+
+/**
+ * The write end of a pipe that is used to notify the parent process
+ * that initialization has finished and that it should detach.
+ */
+static int detach_fd = -1;
+
+void
+daemonize_kill(void)
+{
+ FILE *fp;
+ int pid, ret;
+
+ if (pidfile.IsNull())
+ FatalError("no pid_file specified in the config file");
+
+ fp = FOpen(pidfile, "r");
+ if (fp == nullptr) {
+ const std::string utf8 = pidfile.ToUTF8();
+ FormatFatalSystemError("Unable to open pid file \"%s\"",
+ utf8.c_str());
+ }
+
+ if (fscanf(fp, "%i", &pid) != 1) {
+ const std::string utf8 = pidfile.ToUTF8();
+ FormatFatalError("unable to read the pid from file \"%s\"",
+ utf8.c_str());
+ }
+ fclose(fp);
+
+ ret = kill(pid, SIGTERM);
+ if (ret < 0)
+ FormatFatalSystemError("unable to kill process %i",
+ int(pid));
+
+ exit(EXIT_SUCCESS);
+}
+
+void
+daemonize_close_stdin(void)
+{
+ close(STDIN_FILENO);
+ open("/dev/null", O_RDONLY);
+}
+
+void
+daemonize_set_user(void)
+{
+ if (user_name == nullptr)
+ return;
+
+ /* set gid */
+ if (user_gid != (gid_t)-1 && user_gid != getgid() &&
+ setgid(user_gid) == -1) {
+ FormatFatalSystemError("Failed to set group %d",
+ (int)user_gid);
+ }
+
+#ifdef _BSD_SOURCE
+ /* init supplementary groups
+ * (must be done before we change our uid)
+ */
+ if (!had_group &&
+ /* no need to set the new user's supplementary groups if
+ we are already this user */
+ user_uid != getuid() &&
+ initgroups(user_name, user_gid) == -1) {
+ FormatFatalSystemError("Failed to set supplementary groups "
+ "of user \"%s\"",
+ user_name);
+ }
+#endif
+
+ /* set uid */
+ if (user_uid != (uid_t)-1 && user_uid != getuid() &&
+ setuid(user_uid) == -1) {
+ FormatFatalSystemError("Failed to set user \"%s\"",
+ user_name);
+ }
+}
+
+void
+daemonize_begin(bool detach)
+{
+ /* release the current working directory */
+ if (chdir("/") < 0)
+ FatalError("problems changing to root directory");
+
+ if (!detach)
+ /* the rest of this function deals with detaching the
+ process */
+ return;
+
+ /* do this before daemonizing so we can fail gracefully if we
+ can't write to the pid file */
+ PidFile pidfile2(pidfile);
+
+ /* flush all file handles before duplicating the buffers */
+
+ fflush(nullptr);
+
+ /* create a pipe to synchronize the parent and the child */
+
+ int fds[2];
+ if (pipe(fds) < 0)
+ FatalSystemError("pipe() failed");
+
+ /* move to a child process */
+
+ pid_t pid = fork();
+ if (pid < 0)
+ FatalSystemError("fork() failed");
+
+ if (pid == 0) {
+ /* in the child process */
+
+ pidfile2.Close();
+ close(fds[0]);
+ detach_fd = fds[1];
+
+ /* detach from the current session */
+ setsid();
+
+ /* continue starting MPD */
+ return;
+ }
+
+ /* in the parent process */
+
+ close(fds[1]);
+
+ int result;
+ ssize_t nbytes = read(fds[0], &result, sizeof(result));
+ if (nbytes == (ssize_t)sizeof(result)) {
+ /* the child process was successful */
+ pidfile2.Write(pid);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* something bad happened in the child process */
+
+ pidfile2.Delete(pidfile);
+
+ int status;
+ pid_t pid2 = waitpid(pid, &status, 0);
+ if (pid2 < 0)
+ FatalSystemError("waitpid() failed");
+
+ if (WIFSIGNALED(status))
+ FormatFatalError("MPD died from signal %d%s", WTERMSIG(status),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+
+ exit(WEXITSTATUS(status));
+}
+
+void
+daemonize_commit()
+{
+ if (detach_fd >= 0) {
+ /* tell the parent process to let go of us and exit
+ indicating success */
+ int result = 0;
+ write(detach_fd, &result, sizeof(result));
+ close(detach_fd);
+ } else
+ /* the pidfile was not written by the parent because
+ there is no parent - do it now */
+ PidFile(pidfile).Write();
+}
+
+void
+daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile)
+{
+ if (user) {
+ struct passwd *pwd = getpwnam(user);
+ if (pwd == nullptr)
+ FormatFatalError("no such user \"%s\"", user);
+
+ user_uid = pwd->pw_uid;
+ user_gid = pwd->pw_gid;
+
+ user_name = strdup(user);
+
+ /* this is needed by libs such as arts */
+ setenv("HOME", pwd->pw_dir, true);
+ }
+
+ if (group) {
+ struct group *grp = getgrnam(group);
+ if (grp == nullptr)
+ FormatFatalError("no such group \"%s\"", group);
+ user_gid = grp->gr_gid;
+ had_group = true;
+ }
+
+
+ pidfile = std::move(_pidfile);
+}
+
+void
+daemonize_finish(void)
+{
+ if (!pidfile.IsNull()) {
+ RemoveFile(pidfile);
+ pidfile = AllocatedPath::Null();
+ }
+
+ free(user_name);
+}
+
+#endif
diff --git a/src/unix/Daemon.hxx b/src/unix/Daemon.hxx
new file mode 100644
index 000000000..fe5681511
--- /dev/null
+++ b/src/unix/Daemon.hxx
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DAEMON_HXX
+#define MPD_DAEMON_HXX
+
+class AllocatedPath;
+
+#ifndef WIN32
+void
+daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile);
+#else
+static inline void
+daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile)
+{ (void)user; (void)group; (void)pidfile; }
+#endif
+
+#ifndef WIN32
+void
+daemonize_finish(void);
+#else
+static inline void
+daemonize_finish(void)
+{ /* nop */ }
+#endif
+
+/**
+ * Kill the MPD which is currently running, pid determined from the
+ * pid file.
+ */
+#ifndef WIN32
+void
+daemonize_kill(void);
+#else
+#include "system/FatalError.hxx"
+static inline void
+daemonize_kill(void)
+{
+ FatalError("--kill is not available on WIN32");
+}
+#endif
+
+/**
+ * Close stdin (fd 0) and re-open it as /dev/null.
+ */
+#ifndef WIN32
+void
+daemonize_close_stdin(void);
+#else
+static inline void
+daemonize_close_stdin(void) {}
+#endif
+
+/**
+ * Change to the configured Unix user.
+ */
+#ifndef WIN32
+void
+daemonize_set_user(void);
+#else
+static inline void
+daemonize_set_user(void)
+{ /* nop */ }
+#endif
+
+#ifndef WIN32
+void
+daemonize_begin(bool detach);
+#else
+static inline void
+daemonize_begin(bool detach)
+{ (void)detach; }
+#endif
+
+#ifndef WIN32
+void
+daemonize_commit();
+#else
+static inline void
+daemonize_commit() {}
+#endif
+
+#endif
diff --git a/src/unix/PidFile.hxx b/src/unix/PidFile.hxx
new file mode 100644
index 000000000..a242c7810
--- /dev/null
+++ b/src/unix/PidFile.hxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PID_FILE_HXX
+#define MPD_PID_FILE_HXX
+
+#include "fs/FileSystem.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+class PidFile {
+ FILE *file;
+
+public:
+ PidFile(const AllocatedPath &path):file(nullptr) {
+ if (path.IsNull())
+ return;
+
+ file = FOpen(path, "w");
+ if (file == nullptr) {
+ const std::string utf8 = path.ToUTF8();
+ FormatFatalSystemError("Failed to create pid file \"%s\"",
+ path.c_str());
+ }
+ }
+
+ PidFile(const PidFile &) = delete;
+
+ void Close() {
+ if (file == nullptr)
+ return;
+
+ fclose(file);
+ }
+
+ void Delete(const AllocatedPath &path) {
+ if (file == nullptr) {
+ assert(path.IsNull());
+ return;
+ }
+
+ assert(!path.IsNull());
+
+ fclose(file);
+ RemoveFile(path);
+ }
+
+ void Write(pid_t pid) {
+ if (file == nullptr)
+ return;
+
+ fprintf(file, "%lu\n", (unsigned long)pid);
+ fclose(file);
+ }
+
+ void Write() {
+ if (file == nullptr)
+ return;
+
+ Write(getpid());
+ }
+};
+
+#endif
diff --git a/src/unix/SignalHandlers.cxx b/src/unix/SignalHandlers.cxx
new file mode 100644
index 000000000..4aef4fa71
--- /dev/null
+++ b/src/unix/SignalHandlers.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SignalHandlers.hxx"
+#include "event/SignalMonitor.hxx"
+
+#ifndef WIN32
+
+#include "Log.hxx"
+#include "LogInit.hxx"
+#include "event/Loop.hxx"
+#include "system/FatalError.hxx"
+#include "util/Domain.hxx"
+
+#include <signal.h>
+
+static constexpr Domain signal_handlers_domain("signal_handlers");
+
+static void
+HandleShutdownSignal()
+{
+ SignalMonitorGetEventLoop().Break();
+}
+
+static void
+x_sigaction(int signum, const struct sigaction *act)
+{
+ if (sigaction(signum, act, NULL) < 0)
+ FatalSystemError("sigaction() failed");
+}
+
+static void
+handle_reload_event(void)
+{
+ LogDebug(signal_handlers_domain, "got SIGHUP, reopening log files");
+ cycle_log_files();
+}
+
+#endif
+
+void
+SignalHandlersInit(EventLoop &loop)
+{
+ SignalMonitorInit(loop);
+
+#ifndef WIN32
+ struct sigaction sa;
+
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ x_sigaction(SIGPIPE, &sa);
+
+ SignalMonitorRegister(SIGINT, HandleShutdownSignal);
+ SignalMonitorRegister(SIGTERM, HandleShutdownSignal);
+
+ SignalMonitorRegister(SIGHUP, handle_reload_event);
+#endif
+}
+
+void
+SignalHandlersFinish()
+{
+ SignalMonitorFinish();
+}
diff --git a/src/unix/SignalHandlers.hxx b/src/unix/SignalHandlers.hxx
new file mode 100644
index 000000000..551b373c1
--- /dev/null
+++ b/src/unix/SignalHandlers.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SIGNAL_HANDLERS_HXX
+#define MPD_SIGNAL_HANDLERS_HXX
+
+class EventLoop;
+
+void
+SignalHandlersInit(EventLoop &loop);
+
+void
+SignalHandlersFinish();
+
+#endif
diff --git a/src/util/Alloc.cxx b/src/util/Alloc.cxx
new file mode 100644
index 000000000..ec3579470
--- /dev/null
+++ b/src/util/Alloc.cxx
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "Alloc.hxx"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+gcc_noreturn
+static void
+oom()
+{
+ (void)write(STDERR_FILENO, "Out of memory\n", 14);
+ _exit(1);
+}
+
+void *
+xalloc(size_t size)
+{
+ void *p = malloc(size);
+ if (gcc_unlikely(p == nullptr))
+ oom();
+
+ return p;
+}
+
+void *
+xmemdup(const void *s, size_t size)
+{
+ void *p = xalloc(size);
+ memcpy(p, s, size);
+ return p;
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *p = strdup(s);
+ if (gcc_unlikely(p == nullptr))
+ oom();
+
+ return p;
+}
+
+char *
+xstrndup(const char *s, size_t n)
+{
+#ifdef WIN32
+ char *p = (char *)xalloc(n + 1);
+ memcpy(p, s, n);
+ p[n] = 0;
+#else
+ char *p = strndup(s, n);
+ if (gcc_unlikely(p == nullptr))
+ oom();
+#endif
+
+ return p;
+}
diff --git a/src/util/Alloc.hxx b/src/util/Alloc.hxx
new file mode 100644
index 000000000..15c123b7a
--- /dev/null
+++ b/src/util/Alloc.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ALLOC_HXX
+#define MPD_ALLOC_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+/**
+ * Allocate memory. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc
+void *
+xalloc(size_t size);
+
+/**
+ * Duplicate memory. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+void *
+xmemdup(const void *s, size_t size);
+
+/**
+ * Duplicate a string. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+char *
+xstrdup(const char *s);
+
+/**
+ * Duplicate a string. Use free() to free it.
+ *
+ * This function never fails; in out-of-memory situations, it aborts
+ * the process.
+ */
+gcc_malloc gcc_nonnull_all
+char *
+xstrndup(const char *s, size_t n);
+
+#endif
diff --git a/src/util/ByteReverse.cxx b/src/util/ByteReverse.cxx
index 910c1e2a5..5cc8692a7 100644
--- a/src/util/ByteReverse.cxx
+++ b/src/util/ByteReverse.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/ByteReverse.hxx b/src/util/ByteReverse.hxx
index d6380213a..0c060c0cb 100644
--- a/src/util/ByteReverse.hxx
+++ b/src/util/ByteReverse.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/Cast.hxx b/src/util/Cast.hxx
new file mode 100644
index 000000000..887137da4
--- /dev/null
+++ b/src/util/Cast.hxx
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2013-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CAST_HXX
+#define CAST_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+/**
+ * Offset the given pointer by the specified number of bytes.
+ */
+static inline constexpr void *
+OffsetPointer(void *p, ptrdiff_t offset)
+{
+ return (char *)p + offset;
+}
+
+/**
+ * Offset the given pointer by the specified number of bytes.
+ */
+static inline constexpr const void *
+OffsetPointer(const void *p, ptrdiff_t offset)
+{
+ return (const char *)p + offset;
+}
+
+template<typename T, typename U>
+static inline constexpr T *
+OffsetCast(U *p, ptrdiff_t offset)
+{
+ return reinterpret_cast<T *>(OffsetPointer(p, offset));
+}
+
+template<typename T, typename U>
+static inline constexpr T *
+OffsetCast(const U *p, ptrdiff_t offset)
+{
+ return reinterpret_cast<const T *>(OffsetPointer(p, offset));
+}
+
+template<class C, class A>
+static constexpr inline ptrdiff_t
+ContainerAttributeOffset(const C *null_c, const A C::*p)
+{
+ return ptrdiff_t((const char *)null_c - (const char *)&(null_c->*p));
+}
+
+template<class C, class A>
+static constexpr inline ptrdiff_t
+ContainerAttributeOffset(const A C::*p)
+{
+ return ContainerAttributeOffset<C, A>(nullptr, p);
+}
+
+/**
+ * Cast the given pointer to a struct member to its parent structure.
+ */
+template<class C, class A>
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+constexpr
+#endif
+static inline C &
+ContainerCast(A &a, A C::*member)
+{
+ return *OffsetCast<C, A>(&a, ContainerAttributeOffset<C, A>(member));
+}
+
+/**
+ * Cast the given pointer to a struct member to its parent structure.
+ */
+template<class C, class A>
+#if defined(__clang__) || GCC_CHECK_VERSION(4,7)
+constexpr
+#endif
+static inline const C &
+ContainerCast(const A &a, A C::*member)
+{
+ return *OffsetCast<const C, const A>(&a, ContainerAttributeOffset<C, A>(member));
+}
+
+#endif
diff --git a/src/util/CharUtil.hxx b/src/util/CharUtil.hxx
index dd964f9c3..84a88a94e 100644
--- a/src/util/CharUtil.hxx
+++ b/src/util/CharUtil.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2013 Max Kellermann <max@duempel.org>
+ * Copyright (C) 2011-2014 Max Kellermann <max@duempel.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -27,75 +27,90 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef CHAR_UTIL_HPP
-#define CHAR_UTIL_HPP
+#ifndef CHAR_UTIL_HXX
+#define CHAR_UTIL_HXX
constexpr
static inline bool
IsASCII(const unsigned char ch)
{
- return ch < 0x80;
+ return ch < 0x80;
}
constexpr
static inline bool
IsASCII(const char ch)
{
- return IsASCII((unsigned char)ch);
+ return IsASCII((unsigned char)ch);
}
+constexpr
static inline bool
IsWhitespaceOrNull(const char ch)
{
- return (unsigned char)ch <= 0x20;
+ return (unsigned char)ch <= 0x20;
}
+constexpr
static inline bool
IsWhitespaceNotNull(const char ch)
{
- return ch > 0 && ch <= 0x20;
+ return ch > 0 && ch <= 0x20;
+}
+
+/**
+ * Is the given character whitespace? This calls the faster one of
+ * IsWhitespaceOrNull() or IsWhitespaceNotNull(). Use this if you
+ * want the fastest implementation, and you don't care if a null byte
+ * matches.
+ */
+constexpr
+static inline bool
+IsWhitespaceFast(const char ch)
+{
+ return IsWhitespaceOrNull(ch);
}
constexpr
static inline bool
IsPrintableASCII(char ch)
{
- return (signed char)ch >= 0x20;
+ return (signed char)ch >= 0x20;
}
constexpr
static inline bool
IsDigitASCII(char ch)
{
- return ch >= '0' && ch <= '9';
+ return ch >= '0' && ch <= '9';
}
constexpr
static inline bool
IsUpperAlphaASCII(char ch)
{
- return ch >= 'A' && ch <= 'Z';
+ return ch >= 'A' && ch <= 'Z';
}
constexpr
static inline bool
IsLowerAlphaASCII(char ch)
{
- return ch >= 'a' && ch <= 'z';
+ return ch >= 'a' && ch <= 'z';
}
constexpr
static inline bool
IsAlphaASCII(char ch)
{
- return IsUpperAlphaASCII(ch) || IsLowerAlphaASCII(ch);
+ return IsUpperAlphaASCII(ch) || IsLowerAlphaASCII(ch);
}
constexpr
static inline bool
IsAlphaNumericASCII(char ch)
{
- return IsAlphaASCII(ch) || IsDigitASCII(ch);
+ return IsAlphaASCII(ch) || IsDigitASCII(ch);
}
/**
@@ -106,9 +121,22 @@ constexpr
static inline char
ToUpperASCII(char ch)
{
- return ch >= 'a' && ch <= 'z'
- ? (ch - ('a' - 'A'))
- : ch;
+ return ch >= 'a' && ch <= 'z'
+ ? (ch - ('a' - 'A'))
+ : ch;
+}
+
+/**
+ * Convert the specified ASCII character (0x00..0x7f) to lower case.
+ * Unlike toupper(), it ignores the system locale.
+ */
+constexpr
+static inline char
+ToLowerASCII(char ch)
+{
+ return ch >= 'A' && ch <= 'Z'
+ ? (ch + ('a' - 'A'))
+ : ch;
}
#endif
diff --git a/src/util/CircularBuffer.hxx b/src/util/CircularBuffer.hxx
new file mode 100644
index 000000000..da6f412a5
--- /dev/null
+++ b/src/util/CircularBuffer.hxx
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CIRCULAR_BUFFER_HPP
+#define CIRCULAR_BUFFER_HPP
+
+#include "WritableBuffer.hxx"
+
+#include <assert.h>
+#include <stddef.h>
+
+/**
+ * A circular buffer.
+ *
+ * This class does not manage buffer memory. It will not allocate or
+ * free any memory, it only manages the contents of an existing
+ * buffer given to the constructor.
+ *
+ * Everything between #head and #tail is valid data (may wrap around).
+ * If both are equal, then the buffer is empty. Due to this
+ * implementation detail, the buffer is empty when #size-1 items are
+ * stored; the last buffer cell cannot be used.
+ */
+template<typename T>
+class CircularBuffer {
+public:
+ typedef WritableBuffer<T> Range;
+ typedef typename Range::pointer_type pointer_type;
+ typedef typename Range::size_type size_type;
+
+protected:
+ /**
+ * The next index to be read.
+ */
+ size_type head;
+
+ /**
+ * The next index to be written to.
+ */
+ size_type tail;
+
+ const size_type capacity;
+ const pointer_type data;
+
+public:
+ constexpr CircularBuffer(pointer_type _data, size_type _capacity)
+ :head(0), tail(0), capacity(_capacity), data(_data) {}
+
+ CircularBuffer(const CircularBuffer &other) = delete;
+
+protected:
+ constexpr size_type Next(size_type i) const {
+ return i + 1 == capacity
+ ? 0
+ : i + 1;
+ }
+
+public:
+ void Clear() {
+ head = tail = 0;
+ }
+
+ constexpr size_type GetCapacity() const {
+ return capacity;
+ }
+
+ constexpr bool IsEmpty() const {
+ return head == tail;
+ }
+
+ constexpr bool IsFull() const {
+ return Next(tail) == head;
+ }
+
+ /**
+ * Returns the number of elements stored in this buffer.
+ */
+ constexpr size_type GetSize() const {
+ return head <= tail
+ ? tail - head
+ : capacity - head + tail;
+ }
+
+ /**
+ * Returns the number of elements that can be added to this
+ * buffer.
+ */
+ constexpr size_type GetSpace() const {
+ /* space = capacity - size - 1 */
+ return (head <= tail
+ ? capacity - tail + head
+ : head - tail)
+ - 1;
+ }
+
+ /**
+ * Prepares writing. Returns a buffer range which may be written.
+ * When you are finished, call Append().
+ */
+ Range Write() {
+ assert(head < capacity);
+ assert(tail < capacity);
+
+ size_type end = tail < head
+ ? head - 1
+ /* the "head==0" is there so we don't write
+ the last cell, as this situation cannot be
+ represented by head/tail */
+ : capacity - (head == 0);
+
+ return Range(data + tail, end - tail);
+ }
+
+ /**
+ * Expands the tail of the buffer, after data has been written
+ * to the buffer returned by Write().
+ */
+ void Append(size_type n) {
+ assert(head < capacity);
+ assert(tail < capacity);
+ assert(n < capacity);
+ assert(tail + n <= capacity);
+ assert(head <= tail || tail + n < head);
+
+ tail += n;
+
+ if (tail == capacity) {
+ assert(head > 0);
+ tail = 0;
+ }
+ }
+
+ /**
+ * Return a buffer range which may be read. The buffer pointer is
+ * writable, to allow modifications while parsing.
+ */
+ Range Read() {
+ assert(head < capacity);
+ assert(tail < capacity);
+
+ return Range(data + head, (tail < head ? capacity : tail) - head);
+ }
+
+ /**
+ * Marks a chunk as consumed.
+ */
+ void Consume(size_type n) {
+ assert(head < capacity);
+ assert(tail < capacity);
+ assert(n < capacity);
+ assert(head + n <= capacity);
+ assert(tail < head || head + n <= tail);
+
+ head += n;
+ if (head == capacity)
+ head = 0;
+ }
+};
+
+#endif
diff --git a/src/util/Clamp.hxx b/src/util/Clamp.hxx
new file mode 100644
index 000000000..3217ef9f7
--- /dev/null
+++ b/src/util/Clamp.hxx
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CLAMP_HPP
+#define CLAMP_HPP
+
+#include "Compiler.h"
+
+/**
+ * Clamps the specified value in a range. Returns #min or #max if the
+ * value is outside.
+ */
+template<typename T>
+static inline constexpr const T &
+Clamp(const T &value, const T &min, const T &max)
+{
+ return gcc_unlikely(value < min)
+ ? min
+ : (gcc_unlikely(value > max)
+ ? max : value);
+}
+
+#endif
diff --git a/src/util/ConstBuffer.hxx b/src/util/ConstBuffer.hxx
new file mode 100644
index 000000000..4d0a49e98
--- /dev/null
+++ b/src/util/ConstBuffer.hxx
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2013-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONST_BUFFER_HPP
+#define CONST_BUFFER_HPP
+
+#include "Compiler.h"
+
+#include <cstddef>
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+template<typename T>
+struct ConstBuffer;
+
+template<>
+struct ConstBuffer<void> {
+ typedef size_t size_type;
+ typedef const void *pointer_type;
+ typedef pointer_type const_pointer_type;
+ typedef pointer_type iterator;
+ typedef pointer_type const_iterator;
+
+ pointer_type data;
+ size_type size;
+
+ ConstBuffer() = default;
+
+ constexpr ConstBuffer(std::nullptr_t):data(nullptr), size(0) {}
+
+ constexpr ConstBuffer(pointer_type _data, size_type _size)
+ :data(_data), size(_size) {}
+
+ constexpr static ConstBuffer Null() {
+ return ConstBuffer(nullptr, 0);
+ }
+
+ constexpr static ConstBuffer<void> FromVoid(ConstBuffer<void> other) {
+ return other;
+ }
+
+ constexpr ConstBuffer<void> ToVoid() const {
+ return *this;
+ }
+
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
+
+ constexpr bool IsEmpty() const {
+ return size == 0;
+ }
+};
+
+/**
+ * A reference to a memory area that is read-only.
+ */
+template<typename T>
+struct ConstBuffer {
+ typedef size_t size_type;
+ typedef const T &reference_type;
+ typedef reference_type const_reference_type;
+ typedef const T *pointer_type;
+ typedef pointer_type const_pointer_type;
+ typedef pointer_type iterator;
+ typedef pointer_type const_iterator;
+
+ pointer_type data;
+ size_type size;
+
+ ConstBuffer() = default;
+
+ constexpr ConstBuffer(std::nullptr_t):data(nullptr), size(0) {}
+
+ constexpr ConstBuffer(pointer_type _data, size_type _size)
+ :data(_data), size(_size) {}
+
+ constexpr static ConstBuffer Null() {
+ return ConstBuffer(nullptr, 0);
+ }
+
+ /**
+ * Cast a ConstBuffer<void> to a ConstBuffer<T>. A "void"
+ * buffer records its size in bytes, and when casting to "T",
+ * the assertion below ensures that the size is a multiple of
+ * sizeof(T).
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ static ConstBuffer<T> FromVoid(ConstBuffer<void> other) {
+ static_assert(sizeof(T) > 0, "Empty base type");
+#ifndef NDEBUG
+ assert(other.size % sizeof(T) == 0);
+#endif
+ return ConstBuffer<T>(pointer_type(other.data),
+ other.size / sizeof(T));
+ }
+
+ constexpr ConstBuffer<void> ToVoid() const {
+ static_assert(sizeof(T) > 0, "Empty base type");
+ return ConstBuffer<void>(data, size * sizeof(T));
+ }
+
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
+
+ constexpr bool IsEmpty() const {
+ return size == 0;
+ }
+
+ template<typename U>
+ gcc_pure
+ bool Contains(U &&u) const {
+ for (const auto &i : *this)
+ if (u == i)
+ return true;
+
+ return false;
+ }
+
+ constexpr iterator begin() const {
+ return data;
+ }
+
+ constexpr iterator end() const {
+ return data + size;
+ }
+
+ constexpr const_iterator cbegin() const {
+ return data;
+ }
+
+ constexpr const_iterator cend() const {
+ return data + size;
+ }
+
+#ifdef NDEBUG
+ constexpr
+#endif
+ reference_type operator[](size_type i) const {
+#ifndef NDEBUG
+ assert(i < size);
+#endif
+
+ return data[i];
+ }
+
+ /**
+ * Returns a reference to the first element. Buffer must not
+ * be empty.
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ reference_type front() const {
+#ifndef NDEBUG
+ assert(!IsEmpty());
+#endif
+ return data[0];
+ }
+
+ /**
+ * Returns a reference to the last element. Buffer must not
+ * be empty.
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ reference_type back() const {
+#ifndef NDEBUG
+ assert(!IsEmpty());
+#endif
+ return data[size - 1];
+ }
+
+ /**
+ * Remove the first element (by moving the head pointer, does
+ * not actually modify the buffer). Buffer must not be empty.
+ */
+ void pop_front() {
+#ifndef NDEBUG
+ assert(!IsEmpty());
+#endif
+
+ ++data;
+ --size;
+ }
+
+ /**
+ * Remove the last element (by moving the tail pointer, does
+ * not actually modify the buffer). Buffer must not be empty.
+ */
+ void pop_back() {
+#ifndef NDEBUG
+ assert(!IsEmpty());
+#endif
+
+ --size;
+ }
+
+ /**
+ * Remove the first element and return a reference to it.
+ * Buffer must not be empty.
+ */
+ reference_type shift() {
+ reference_type result = front();
+ pop_front();
+ return result;
+ }
+
+ void skip_front(size_type n) {
+#ifndef NDEBUG
+ assert(size >= n);
+#endif
+
+ data += n;
+ size -= n;
+ }
+};
+
+#endif
diff --git a/src/util/Domain.hxx b/src/util/Domain.hxx
index bbdbf8371..6dce7b731 100644
--- a/src/util/Domain.hxx
+++ b/src/util/Domain.hxx
@@ -1,24 +1,34 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_DOMAIN_HXX
-#define MPD_DOMAIN_HXX
+#ifndef DOMAIN_HXX
+#define DOMAIN_HXX
class Domain {
const char *const name;
diff --git a/src/util/DynamicFifoBuffer.hxx b/src/util/DynamicFifoBuffer.hxx
new file mode 100644
index 000000000..c1e5d1b94
--- /dev/null
+++ b/src/util/DynamicFifoBuffer.hxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2013 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FIFO_BUFFER_HPP
+#define FIFO_BUFFER_HPP
+
+#include "ForeignFifoBuffer.hxx"
+
+/**
+ * A first-in-first-out buffer: you can append data at the end, and
+ * read data from the beginning. This class automatically shifts the
+ * buffer as needed. It is not thread safe.
+ */
+template<typename T>
+class DynamicFifoBuffer : protected ForeignFifoBuffer<T> {
+public:
+ typedef typename ForeignFifoBuffer<T>::size_type size_type;
+ typedef typename ForeignFifoBuffer<T>::pointer_type pointer_type;
+ typedef typename ForeignFifoBuffer<T>::const_pointer_type const_pointer_type;
+ typedef typename ForeignFifoBuffer<T>::Range Range;
+
+ explicit DynamicFifoBuffer(size_type _capacity)
+ :ForeignFifoBuffer<T>(new T[_capacity], _capacity) {}
+ ~DynamicFifoBuffer() {
+ delete[] GetBuffer();
+ }
+
+ DynamicFifoBuffer(const DynamicFifoBuffer &) = delete;
+
+ using ForeignFifoBuffer<T>::GetCapacity;
+ using ForeignFifoBuffer<T>::Clear;
+ using ForeignFifoBuffer<T>::IsEmpty;
+ using ForeignFifoBuffer<T>::IsFull;
+ using ForeignFifoBuffer<T>::GetAvailable;
+ using ForeignFifoBuffer<T>::Read;
+ using ForeignFifoBuffer<T>::Consume;
+ using ForeignFifoBuffer<T>::Write;
+ using ForeignFifoBuffer<T>::Append;
+
+ void Grow(size_type new_capacity) {
+ assert(new_capacity > GetCapacity());
+
+ T *old_data = GetBuffer();
+ T *new_data = new T[new_capacity];
+ ForeignFifoBuffer<T>::MoveBuffer(new_data, new_capacity);
+ delete[] old_data;
+ }
+
+ void WantWrite(size_type n) {
+ if (ForeignFifoBuffer<T>::WantWrite(n))
+ /* we already have enough space */
+ return;
+
+ const size_type in_use = GetAvailable();
+ const size_type required_capacity = in_use + n;
+ size_type new_capacity = GetCapacity();
+ do {
+ new_capacity <<= 1;
+ } while (new_capacity < required_capacity);
+
+ Grow(new_capacity);
+ }
+
+ /**
+ * Write data to the buffer, growing it as needed. Returns a
+ * writable pointer.
+ */
+ pointer_type Write(size_type n) {
+ WantWrite(n);
+ return Write().data;
+ }
+
+ /**
+ * Append data to the buffer, growing it as needed.
+ */
+ void Append(const_pointer_type p, size_type n) {
+ std::copy_n(p, n, Write(n));
+ Append(n);
+ }
+
+protected:
+ using ForeignFifoBuffer<T>::GetBuffer;
+};
+
+#endif
diff --git a/src/util/Error.cxx b/src/util/Error.cxx
index 5675f4d81..92b2cc5d0 100644
--- a/src/util/Error.cxx
+++ b/src/util/Error.cxx
@@ -1,31 +1,44 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Error.hxx"
#include "Domain.hxx"
+#ifdef WIN32
#include <glib.h>
+#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
+#include <string.h>
const Domain errno_domain("errno");
@@ -70,7 +83,7 @@ Error::FormatPrefix(const char *fmt, ...)
void
Error::SetErrno(int e)
{
- Set(errno_domain, e, g_strerror(e));
+ Set(errno_domain, e, strerror(e));
}
void
@@ -82,7 +95,7 @@ Error::SetErrno()
void
Error::SetErrno(int e, const char *prefix)
{
- Format(errno_domain, e, "%s: %s", prefix, g_strerror(e));
+ Format(errno_domain, e, "%s: %s", prefix, strerror(e));
}
void
@@ -120,11 +133,42 @@ Error::FormatErrno(const char *fmt, ...)
#ifdef WIN32
void
-Error::SetLastError(const char *prefix)
+Error::SetLastError(DWORD _code, const char *prefix)
{
- DWORD _code = GetLastError();
const char *msg = g_win32_error_message(_code);
Format(win32_domain, int(_code), "%s: %s", prefix, msg);
}
+void
+Error::SetLastError(const char *prefix)
+{
+ SetLastError(GetLastError(), prefix);
+}
+
+void
+Error::FormatLastError(DWORD _code, const char *fmt, ...)
+{
+ char buffer[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ SetLastError(_code, buffer);
+}
+
+void
+Error::FormatLastError(const char *fmt, ...)
+{
+ DWORD _code = GetLastError();
+
+ char buffer[1024];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ SetLastError(_code, buffer);
+}
+
#endif
diff --git a/src/util/Error.hxx b/src/util/Error.hxx
index ec8867c6c..ab66ae5cb 100644
--- a/src/util/Error.hxx
+++ b/src/util/Error.hxx
@@ -1,30 +1,40 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_ERROR_HXX
-#define MPD_ERROR_HXX
+#ifndef ERROR_HXX
+#define ERROR_HXX
#include "check.h"
#include "Compiler.h"
#include <string>
-#include <algorithm>
+#include <utility>
#include <assert.h>
@@ -141,17 +151,29 @@ public:
message.insert(0, prefix);
}
+ gcc_printf(2,3)
void FormatPrefix(const char *fmt, ...);
void SetErrno(int e);
void SetErrno();
void SetErrno(int e, const char *prefix);
void SetErrno(const char *prefix);
+
+ gcc_printf(2,3)
void FormatErrno(const char *prefix, ...);
+
+ gcc_printf(3,4)
void FormatErrno(int e, const char *prefix, ...);
#ifdef WIN32
+ void SetLastError(DWORD _code, const char *prefix);
void SetLastError(const char *prefix);
+
+ gcc_printf(3,4)
+ void FormatLastError(DWORD code, const char *fmt, ...);
+
+ gcc_printf(2,3)
+ void FormatLastError(const char *fmt, ...);
#endif
};
diff --git a/src/util/FifoBuffer.hxx b/src/util/FifoBuffer.hxx
deleted file mode 100644
index 75d2d2ef2..000000000
--- a/src/util/FifoBuffer.hxx
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2003-2010 Max Kellermann <max@duempel.org>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef FIFO_BUFFER_HPP
-#define FIFO_BUFFER_HPP
-
-#include "WritableBuffer.hxx"
-
-#include <utility>
-#include <algorithm>
-
-#include <assert.h>
-#include <stddef.h>
-
-/**
- * A first-in-first-out buffer: you can append data at the end, and
- * read data from the beginning. This class automatically shifts the
- * buffer as needed. It is not thread safe.
- */
-template<class T, size_t size>
-class FifoBuffer {
-public:
- typedef size_t size_type;
-
-public:
- typedef WritableBuffer<T> Range;
-
-protected:
- size_type head, tail;
- T data[size];
-
-public:
- constexpr
- FifoBuffer():head(0), tail(0) {}
-
-protected:
- void Shift() {
- if (head == 0)
- return;
-
- assert(head <= size);
- assert(tail <= size);
- assert(tail >= head);
-
- std::move(data + head, data + tail, data);
-
- tail -= head;
- head = 0;
- }
-
-public:
- void Clear() {
- head = tail = 0;
- }
-
- bool IsEmpty() const {
- return head == tail;
- }
-
- bool IsFull() const {
- return head == 0 && tail == size;
- }
-
- /**
- * Prepares writing. Returns a buffer range which may be written.
- * When you are finished, call append().
- */
- Range Write() {
- Shift();
- return Range(data + tail, size - tail);
- }
-
- /**
- * Expands the tail of the buffer, after data has been written to
- * the buffer returned by write().
- */
- void Append(size_type n) {
- assert(tail <= size);
- assert(n <= size);
- assert(tail + n <= size);
-
- tail += n;
- }
-
- /**
- * Return a buffer range which may be read. The buffer pointer is
- * writable, to allow modifications while parsing.
- */
- Range Read() {
- return Range(data + head, tail - head);
- }
-
- /**
- * Marks a chunk as consumed.
- */
- void Consume(size_type n) {
- assert(tail <= size);
- assert(head <= tail);
- assert(n <= tail);
- assert(head + n <= tail);
-
- head += n;
- }
-};
-
-#endif
diff --git a/src/util/ForeignFifoBuffer.hxx b/src/util/ForeignFifoBuffer.hxx
new file mode 100644
index 000000000..b829fb030
--- /dev/null
+++ b/src/util/ForeignFifoBuffer.hxx
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2003-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FOREIGN_FIFO_BUFFER_HXX
+#define FOREIGN_FIFO_BUFFER_HXX
+
+#include "WritableBuffer.hxx"
+
+#include <utility>
+#include <algorithm>
+
+#include <cstddef>
+
+#include <assert.h>
+
+/**
+ * A first-in-first-out buffer: you can append data at the end, and
+ * read data from the beginning. This class automatically shifts the
+ * buffer as needed. It is not thread safe.
+ *
+ * This class does not manage buffer memory. It will not allocate or
+ * free any memory, it only manages the contents of an existing buffer
+ * given to the constructor.
+ */
+template<typename T>
+class ForeignFifoBuffer {
+public:
+ typedef size_t size_type;
+ typedef WritableBuffer<T> Range;
+ typedef typename Range::pointer_type pointer_type;
+ typedef typename Range::const_pointer_type const_pointer_type;
+
+protected:
+ size_type head, tail, capacity;
+ T *data;
+
+public:
+ explicit constexpr ForeignFifoBuffer(std::nullptr_t n)
+ :head(0), tail(0), capacity(0), data(n) {}
+
+ constexpr ForeignFifoBuffer(T *_data, size_type _capacity)
+ :head(0), tail(0), capacity(_capacity), data(_data) {}
+
+ ForeignFifoBuffer(ForeignFifoBuffer &&src)
+ :head(src.head), tail(src.tail),
+ capacity(src.capacity), data(src.data) {
+ src.SetNull();
+ }
+
+ ForeignFifoBuffer &operator=(ForeignFifoBuffer &&src) {
+ head = src.head;
+ tail = src.tail;
+ capacity = src.capacity;
+ data = src.data;
+ src.SetNull();
+ return *this;
+ }
+
+ void Swap(ForeignFifoBuffer<T> &other) {
+ std::swap(head, other.head);
+ std::swap(tail, other.tail);
+ std::swap(capacity, other.capacity);
+ std::swap(data, other.data);
+ }
+
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
+
+ constexpr bool IsDefined() const {
+ return !IsNull();
+ }
+
+ T *GetBuffer() {
+ return data;
+ }
+
+ constexpr size_type GetCapacity() const {
+ return capacity;
+ }
+
+ void SetNull() {
+ head = tail = 0;
+ capacity = 0;
+ data = nullptr;
+ }
+
+ void SetBuffer(T *_data, size_type _capacity) {
+ assert(_data != nullptr);
+ assert(_capacity > 0);
+
+ head = tail = 0;
+ capacity = _capacity;
+ data = _data;
+ }
+
+ void MoveBuffer(T *new_data, size_type new_capacity) {
+ assert(new_capacity >= tail - head);
+
+ std::move(data + head, data + tail, new_data);
+ data = new_data;
+ capacity = new_capacity;
+ tail -= head;
+ head = 0;
+ }
+
+ void Clear() {
+ head = tail = 0;
+ }
+
+ constexpr bool IsEmpty() const {
+ return head == tail;
+ }
+
+ constexpr bool IsFull() const {
+ return head == 0 && tail == capacity;
+ }
+
+ /**
+ * Prepares writing. Returns a buffer range which may be written.
+ * When you are finished, call append().
+ */
+ Range Write() {
+ if (IsEmpty())
+ Clear();
+ else if (tail == capacity)
+ Shift();
+
+ return Range(data + tail, capacity - tail);
+ }
+
+ bool WantWrite(size_type n) {
+ if (tail + n <= capacity)
+ /* enough space after the tail */
+ return true;
+
+ const size_type in_use = tail - head;
+ const size_type required_capacity = in_use + n;
+ if (required_capacity > capacity)
+ return false;
+
+ Shift();
+ assert(tail + n <= capacity);
+ return true;
+ }
+
+ /**
+ * Expands the tail of the buffer, after data has been written to
+ * the buffer returned by write().
+ */
+ void Append(size_type n) {
+ assert(tail <= capacity);
+ assert(n <= capacity);
+ assert(tail + n <= capacity);
+
+ tail += n;
+ }
+
+ constexpr size_type GetAvailable() const {
+ return tail - head;
+ }
+
+ /**
+ * Return a buffer range which may be read. The buffer pointer is
+ * writable, to allow modifications while parsing.
+ */
+ constexpr Range Read() const {
+ return Range(data + head, tail - head);
+ }
+
+ /**
+ * Marks a chunk as consumed.
+ */
+ void Consume(size_type n) {
+ assert(tail <= capacity);
+ assert(head <= tail);
+ assert(n <= tail);
+ assert(head + n <= tail);
+
+ head += n;
+ }
+
+ size_type Read(pointer_type p, size_type n) {
+ auto range = Read();
+ if (n > range.size)
+ n = range.size;
+ std::copy_n(range.data, n, p);
+ Consume(n);
+ return n;
+ }
+
+ /**
+ * Move as much data as possible from the specified buffer.
+ *
+ * @return the number of items moved
+ */
+ size_type MoveFrom(ForeignFifoBuffer<T> &src) {
+ auto r = src.Read();
+ auto w = Write();
+ size_t n = std::min(r.size, w.size);
+
+ std::move(r.data, r.data + n, w.data);
+ Append(n);
+ src.Consume(n);
+ return n;
+ }
+
+protected:
+ void Shift() {
+ if (head == 0)
+ return;
+
+ assert(head <= capacity);
+ assert(tail <= capacity);
+ assert(tail >= head);
+
+ std::move(data + head, data + tail, data);
+
+ tail -= head;
+ head = 0;
+ }
+};
+
+#endif
diff --git a/src/util/FormatString.cxx b/src/util/FormatString.cxx
index c13d0fb52..d222a505c 100644
--- a/src/util/FormatString.cxx
+++ b/src/util/FormatString.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,10 +19,13 @@
#include "FormatString.hxx"
-#include <string.h>
#include <stdio.h>
#include <stdlib.h>
+#ifdef WIN32
+#include <string.h>
+#endif
+
char *
FormatNewV(const char *fmt, va_list args)
{
diff --git a/src/util/FormatString.hxx b/src/util/FormatString.hxx
index bb4263107..dc1ac3c67 100644
--- a/src/util/FormatString.hxx
+++ b/src/util/FormatString.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx
index d1c55c965..1da049f2f 100644
--- a/src/util/HugeAllocator.cxx
+++ b/src/util/HugeAllocator.cxx
@@ -1,20 +1,30 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "HugeAllocator.hxx"
diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx
index f44a6e3b8..fa45e5610 100644
--- a/src/util/HugeAllocator.hxx
+++ b/src/util/HugeAllocator.hxx
@@ -1,24 +1,34 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_HUGE_ALLOCATOR_HXX
-#define MPD_HUGE_ALLOCATOR_HXX
+#ifndef HUGE_ALLOCATOR_HXX
+#define HUGE_ALLOCATOR_HXX
#include "Compiler.h"
@@ -53,6 +63,31 @@ HugeFree(void *p, size_t size);
void
HugeDiscard(void *p, size_t size);
+#elif defined(WIN32)
+#include <windows.h>
+
+gcc_malloc
+static inline void *
+HugeAllocate(size_t size)
+{
+ // TODO: use MEM_LARGE_PAGES
+ return VirtualAlloc(nullptr, size,
+ MEM_COMMIT|MEM_RESERVE,
+ PAGE_READWRITE);
+}
+
+static inline void
+HugeFree(void *p, gcc_unused size_t size)
+{
+ VirtualFree(p, 0, MEM_RELEASE);
+}
+
+static inline void
+HugeDiscard(void *p, size_t size)
+{
+ VirtualAlloc(p, size, MEM_RESET, PAGE_NOACCESS);
+}
+
#else
/* not Linux: fall back to standard C calls */
diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx
index 0f90ebb2e..b0aac913c 100644
--- a/src/util/LazyRandomEngine.cxx
+++ b/src/util/LazyRandomEngine.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx
index bfe4bb60c..4156b3bb1 100644
--- a/src/util/LazyRandomEngine.hxx
+++ b/src/util/LazyRandomEngine.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/OptionDef.hxx b/src/util/OptionDef.hxx
new file mode 100644
index 000000000..dd82154c4
--- /dev/null
+++ b/src/util/OptionDef.hxx
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UTIL_OPTIONDEF_HXX
+#define MPD_UTIL_OPTIONDEF_HXX
+
+/**
+ * Command line option definition.
+ */
+class OptionDef
+{
+ const char *long_option;
+ char short_option;
+ const char *desc;
+public:
+ constexpr OptionDef(const char *_long_option, const char *_desc)
+ : long_option(_long_option),
+ short_option(0),
+ desc(_desc) { }
+
+ constexpr OptionDef(const char *_long_option,
+ char _short_option, const char *_desc)
+ : long_option(_long_option),
+ short_option(_short_option),
+ desc(_desc) { }
+
+ bool HasLongOption() const { return long_option != nullptr; }
+ bool HasShortOption() const { return short_option != 0; }
+ bool HasDescription() const { return desc != nullptr; }
+
+ const char *GetLongOption() const {
+ assert(HasLongOption());
+ return long_option;
+ }
+
+ char GetShortOption() const {
+ assert(HasShortOption());
+ return short_option;
+ }
+
+ const char *GetDescription() const {
+ assert(HasDescription());
+ return desc;
+ }
+};
+
+#endif
diff --git a/src/util/OptionParser.cxx b/src/util/OptionParser.cxx
new file mode 100644
index 000000000..b10008527
--- /dev/null
+++ b/src/util/OptionParser.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "OptionParser.hxx"
+#include "OptionDef.hxx"
+
+#include <string.h>
+
+bool OptionParser::CheckOption(const OptionDef &opt)
+{
+ assert(option != nullptr);
+
+ if (is_long)
+ return opt.HasLongOption() &&
+ strcmp(option, opt.GetLongOption()) == 0;
+
+ return opt.HasShortOption() &&
+ option[0] == opt.GetShortOption() &&
+ option[1] == '\0';
+}
+
+bool OptionParser::ParseNext()
+{
+ assert(HasEntries());
+ char *arg = *argv;
+ ++argv;
+ --argc;
+ if (arg[0] == '-') {
+ if (arg[1] == '-') {
+ option = arg + 2;
+ is_long = true;
+ }
+ else {
+ option = arg + 1;
+ is_long = false;
+ }
+ option_raw = arg;
+ return true;
+ }
+ option = nullptr;
+ option_raw = nullptr;
+ return false;
+}
diff --git a/src/util/OptionParser.hxx b/src/util/OptionParser.hxx
new file mode 100644
index 000000000..b9d34adbb
--- /dev/null
+++ b/src/util/OptionParser.hxx
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_UTIL_OPTIONPARSER_HXX
+#define MPD_UTIL_OPTIONPARSER_HXX
+
+#include <assert.h>
+
+class OptionDef;
+
+/**
+ * Command line option parser.
+ */
+class OptionParser
+{
+ int argc;
+ char **argv;
+ char *option;
+ char *option_raw;
+ bool is_long;
+public:
+ /**
+ * Constructs #OptionParser.
+ */
+ OptionParser(int _argc, char **_argv)
+ : argc(_argc - 1), argv(_argv + 1),
+ option(nullptr), option_raw(nullptr), is_long(false) { }
+
+ /**
+ * Checks if there are command line entries to process.
+ */
+ bool HasEntries() const { return argc > 0; }
+
+ /**
+ * Gets the last parsed option.
+ */
+ char *GetOption() {
+ assert(option_raw != nullptr);
+ return option_raw;
+ }
+
+ /**
+ * Checks if current option is a specified option.
+ */
+ bool CheckOption(const OptionDef& opt);
+
+ /**
+ * Checks if current option is a specified option
+ * or specified alternative option.
+ */
+ bool CheckOption(const OptionDef& opt, const OptionDef &alt_opt) {
+ return CheckOption(opt) || CheckOption(alt_opt);
+ }
+
+ /**
+ * Parses current command line entry.
+ * Returns true on success, false otherwise.
+ * Regardless of result, advances current position to the next
+ * command line entry.
+ */
+ bool ParseNext();
+
+ /**
+ * Checks if specified string is a command line option.
+ */
+ static bool IsOption(const char *s) {
+ assert(s != nullptr);
+ return s[0] == '-';
+ }
+};
+
+#endif
diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx
index d9b193dd1..e4624bbec 100644
--- a/src/util/PeakBuffer.cxx
+++ b/src/util/PeakBuffer.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,46 +18,39 @@
*/
#include "PeakBuffer.hxx"
-#include "HugeAllocator.hxx"
-#include "fifo_buffer.h"
+#include "DynamicFifoBuffer.hxx"
#include <algorithm>
#include <assert.h>
-#include <stdint.h>
#include <string.h>
PeakBuffer::~PeakBuffer()
{
- if (normal_buffer != nullptr)
- fifo_buffer_free(normal_buffer);
-
- if (peak_buffer != nullptr)
- HugeFree(peak_buffer, peak_size);
+ delete normal_buffer;
+ delete peak_buffer;
}
bool
PeakBuffer::IsEmpty() const
{
- return (normal_buffer == nullptr ||
- fifo_buffer_is_empty(normal_buffer)) &&
- (peak_buffer == nullptr ||
- fifo_buffer_is_empty(peak_buffer));
+ return (normal_buffer == nullptr || normal_buffer->IsEmpty()) &&
+ (peak_buffer == nullptr || peak_buffer->IsEmpty());
}
-const void *
-PeakBuffer::Read(size_t *length_r) const
+WritableBuffer<void>
+PeakBuffer::Read() const
{
if (normal_buffer != nullptr) {
- const void *p = fifo_buffer_read(normal_buffer, length_r);
- if (p != nullptr)
- return p;
+ const auto p = normal_buffer->Read();
+ if (!p.IsEmpty())
+ return p.ToVoid();
}
if (peak_buffer != nullptr) {
- const void *p = fifo_buffer_read(peak_buffer, length_r);
- if (p != nullptr)
- return p;
+ const auto p = peak_buffer->Read();
+ if (!p.IsEmpty())
+ return p.ToVoid();
}
return nullptr;
@@ -66,15 +59,15 @@ PeakBuffer::Read(size_t *length_r) const
void
PeakBuffer::Consume(size_t length)
{
- if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) {
- fifo_buffer_consume(normal_buffer, length);
+ if (normal_buffer != nullptr && !normal_buffer->IsEmpty()) {
+ normal_buffer->Consume(length);
return;
}
- if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) {
- fifo_buffer_consume(peak_buffer, length);
- if (fifo_buffer_is_empty(peak_buffer)) {
- HugeFree(peak_buffer, peak_size);
+ if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) {
+ peak_buffer->Consume(length);
+ if (peak_buffer->IsEmpty()) {
+ delete peak_buffer;
peak_buffer = nullptr;
}
@@ -83,7 +76,7 @@ PeakBuffer::Consume(size_t length)
}
static size_t
-AppendTo(fifo_buffer *buffer, const void *data, size_t length)
+AppendTo(DynamicFifoBuffer<uint8_t> &buffer, const void *data, size_t length)
{
assert(data != nullptr);
assert(length > 0);
@@ -91,14 +84,13 @@ AppendTo(fifo_buffer *buffer, const void *data, size_t length)
size_t total = 0;
do {
- size_t max_length;
- void *p = fifo_buffer_write(buffer, &max_length);
- if (p == nullptr)
+ const auto p = buffer.Write();
+ if (p.IsEmpty())
break;
- const size_t nbytes = std::min(length, max_length);
- memcpy(p, data, nbytes);
- fifo_buffer_append(buffer, nbytes);
+ const size_t nbytes = std::min(length, p.size);
+ memcpy(p.data, data, nbytes);
+ buffer.Append(nbytes);
data = (const uint8_t *)data + nbytes;
length -= nbytes;
@@ -114,15 +106,15 @@ PeakBuffer::Append(const void *data, size_t length)
if (length == 0)
return true;
- if (peak_buffer != nullptr && !fifo_buffer_is_empty(peak_buffer)) {
- size_t nbytes = AppendTo(peak_buffer, data, length);
+ if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) {
+ size_t nbytes = AppendTo(*peak_buffer, data, length);
return nbytes == length;
}
if (normal_buffer == nullptr)
- normal_buffer = fifo_buffer_new(normal_size);
+ normal_buffer = new DynamicFifoBuffer<uint8_t>(normal_size);
- size_t nbytes = AppendTo(normal_buffer, data, length);
+ size_t nbytes = AppendTo(*normal_buffer, data, length);
if (nbytes > 0) {
data = (const uint8_t *)data + nbytes;
length -= nbytes;
@@ -132,13 +124,11 @@ PeakBuffer::Append(const void *data, size_t length)
if (peak_buffer == nullptr) {
if (peak_size > 0)
- peak_buffer = (fifo_buffer *)HugeAllocate(peak_size);
+ peak_buffer = new DynamicFifoBuffer<uint8_t>(peak_size);
if (peak_buffer == nullptr)
return false;
-
- fifo_buffer_init(peak_buffer, peak_size);
}
- nbytes = AppendTo(peak_buffer, data, length);
+ nbytes = AppendTo(*peak_buffer, data, length);
return nbytes == length;
}
diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx
index a3f385e3e..702a3dee0 100644
--- a/src/util/PeakBuffer.hxx
+++ b/src/util/PeakBuffer.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,11 +20,14 @@
#ifndef MPD_PEAK_BUFFER_HXX
#define MPD_PEAK_BUFFER_HXX
+#include "WritableBuffer.hxx"
#include "Compiler.h"
#include <stddef.h>
+#include <stdint.h>
-struct fifo_buffer;
+template<typename T> struct WritableBuffer;
+template<typename T> class DynamicFifoBuffer;
/**
* A FIFO-like buffer that will allocate more memory on demand to
@@ -34,7 +37,7 @@ struct fifo_buffer;
class PeakBuffer {
size_t normal_size, peak_size;
- fifo_buffer *normal_buffer, *peak_buffer;
+ DynamicFifoBuffer<uint8_t> *normal_buffer, *peak_buffer;
public:
PeakBuffer(size_t _normal_size, size_t _peak_size)
@@ -57,7 +60,9 @@ public:
gcc_pure
bool IsEmpty() const;
- const void *Read(size_t *length_r) const;
+ gcc_pure
+ WritableBuffer<void> Read() const;
+
void Consume(size_t length);
bool Append(const void *data, size_t length);
diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx
index dff850036..02ef8818c 100644
--- a/src/util/RefCount.hxx
+++ b/src/util/RefCount.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx
index 6cde75f34..63ca087ae 100644
--- a/src/util/SliceBuffer.hxx
+++ b/src/util/SliceBuffer.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/SplitString.cxx b/src/util/SplitString.cxx
new file mode 100644
index 000000000..75e799279
--- /dev/null
+++ b/src/util/SplitString.cxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "SplitString.hxx"
+
+#include <string.h>
+
+SplitString::SplitString(const char *s, char separator)
+ :first(nullptr)
+{
+ const char *x = strchr(s, separator);
+ if (x == nullptr)
+ return;
+
+ size_t length = x - s;
+ second = x + 1;
+
+ first = new char[length + 1];
+ memcpy(first, s, length);
+ first[length] = 0;
+}
diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx
new file mode 100644
index 000000000..96ffb21ec
--- /dev/null
+++ b/src/util/SplitString.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SPLIT_STRING_HXX
+#define MPD_SPLIT_STRING_HXX
+
+#include "Compiler.h"
+
+#include <assert.h>
+
+/**
+ * Split a given constant string at a separator character. Duplicates
+ * the first part to be able to null-terminate it.
+ */
+class SplitString {
+ char *first;
+ const char *second;
+
+public:
+ SplitString(const char *s, char separator);
+
+ ~SplitString() {
+ delete[] first;
+ }
+
+ /**
+ * Was the separator found?
+ */
+ bool IsDefined() const {
+ return first != nullptr;
+ }
+
+ /**
+ * Is the first part empty?
+ */
+ bool IsEmpty() const {
+ assert(IsDefined());
+
+ return *first == 0;
+ }
+
+ const char *GetFirst() const {
+ assert(IsDefined());
+
+ return first;
+ }
+
+ const char *GetSecond() const {
+ assert(IsDefined());
+
+ return second;
+ }
+};
+
+#endif
diff --git a/src/util/StaticFifoBuffer.hxx b/src/util/StaticFifoBuffer.hxx
new file mode 100644
index 000000000..40668d152
--- /dev/null
+++ b/src/util/StaticFifoBuffer.hxx
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2003-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STATIC_FIFO_BUFFER_HPP
+#define STATIC_FIFO_BUFFER_HPP
+
+#include "WritableBuffer.hxx"
+
+#include <utility>
+#include <algorithm>
+
+#include <assert.h>
+#include <stddef.h>
+
+/**
+ * A first-in-first-out buffer: you can append data at the end, and
+ * read data from the beginning. This class automatically shifts the
+ * buffer as needed. It is not thread safe.
+ */
+template<class T, size_t size>
+class StaticFifoBuffer {
+public:
+ typedef size_t size_type;
+
+public:
+ typedef WritableBuffer<T> Range;
+
+protected:
+ size_type head, tail;
+ T data[size];
+
+public:
+ constexpr
+ StaticFifoBuffer():head(0), tail(0) {}
+
+ void Shift() {
+ if (head == 0)
+ return;
+
+ assert(head <= size);
+ assert(tail <= size);
+ assert(tail >= head);
+
+ std::move(data + head, data + tail, data);
+
+ tail -= head;
+ head = 0;
+ }
+
+ void Clear() {
+ head = tail = 0;
+ }
+
+ bool IsEmpty() const {
+ return head == tail;
+ }
+
+ bool IsFull() const {
+ return head == 0 && tail == size;
+ }
+
+ /**
+ * Prepares writing. Returns a buffer range which may be written.
+ * When you are finished, call append().
+ */
+ Range Write() {
+ if (IsEmpty())
+ Clear();
+ else if (tail == size)
+ Shift();
+
+ return Range(data + tail, size - tail);
+ }
+
+ /**
+ * Expands the tail of the buffer, after data has been written to
+ * the buffer returned by write().
+ */
+ void Append(size_type n) {
+ assert(tail <= size);
+ assert(n <= size);
+ assert(tail + n <= size);
+
+ tail += n;
+ }
+
+ /**
+ * Return a buffer range which may be read. The buffer pointer is
+ * writable, to allow modifications while parsing.
+ */
+ Range Read() {
+ return Range(data + head, tail - head);
+ }
+
+ /**
+ * Marks a chunk as consumed.
+ */
+ void Consume(size_type n) {
+ assert(tail <= size);
+ assert(head <= tail);
+ assert(n <= tail);
+ assert(head + n <= tail);
+
+ head += n;
+ }
+};
+
+#endif
diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx
index 7e295bf90..bcade2b3b 100644
--- a/src/util/StringUtil.cxx
+++ b/src/util/StringUtil.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,10 +21,13 @@
#include "CharUtil.hxx"
#include "ASCII.hxx"
+#include <algorithm>
+
#include <assert.h>
+#include <string.h>
const char *
-strchug_fast(const char *p)
+StripLeft(const char *p)
{
while (IsWhitespaceNotNull(*p))
++p;
@@ -32,6 +35,79 @@ strchug_fast(const char *p)
return p;
}
+const char *
+StripLeft(const char *p, const char *end)
+{
+ while (p < end && IsWhitespaceOrNull(*p))
+ ++p;
+
+ return p;
+}
+
+const char *
+StripRight(const char *p, const char *end)
+{
+ while (end > p && IsWhitespaceOrNull(end[-1]))
+ --end;
+
+ return end;
+}
+
+size_t
+StripRight(const char *p, size_t length)
+{
+ while (length > 0 && IsWhitespaceOrNull(p[length - 1]))
+ --length;
+
+ return length;
+}
+
+void
+StripRight(char *p)
+{
+ size_t old_length = strlen(p);
+ size_t new_length = StripRight(p, old_length);
+ p[new_length] = 0;
+}
+
+char *
+Strip(char *p)
+{
+ p = StripLeft(p);
+ StripRight(p);
+ return p;
+}
+
+bool
+StringStartsWith(const char *haystack, const char *needle)
+{
+ const size_t length = strlen(needle);
+ return memcmp(haystack, needle, length) == 0;
+}
+
+bool
+StringEndsWith(const char *haystack, const char *needle)
+{
+ const size_t haystack_length = strlen(haystack);
+ const size_t needle_length = strlen(needle);
+
+ return haystack_length >= needle_length &&
+ memcmp(haystack + haystack_length - needle_length,
+ needle, needle_length) == 0;
+}
+
+char *
+CopyString(char *gcc_restrict dest, const char *gcc_restrict src, size_t size)
+{
+ size_t length = strlen(src);
+ if (length >= size)
+ length = size - 1;
+
+ char *p = std::copy(src, src + length, dest);
+ *p = '\0';
+ return p;
+}
+
bool
string_array_contains(const char *const* haystack, const char *needle)
{
diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx
index 1c67910a9..9beda5441 100644
--- a/src/util/StringUtil.hxx
+++ b/src/util/StringUtil.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,24 +22,86 @@
#include "Compiler.h"
+#include <stddef.h>
+
/**
* 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(const char *p);
+StripLeft(const char *p);
gcc_pure
static inline char *
-strchug_fast(char *p)
+StripLeft(char *p)
{
- return const_cast<char *>(strchug_fast((const char *)p));
+ return const_cast<char *>(StripLeft((const char *)p));
}
+gcc_pure
+const char *
+StripLeft(const char *p, const char *end);
+
+/**
+ * Determine the string's end as if it was stripped on the right side.
+ */
+gcc_pure
+const char *
+StripRight(const char *p, const char *end);
+
+/**
+ * Determine the string's end as if it was stripped on the right side.
+ */
+gcc_pure
+static inline char *
+StripRight(char *p, char *end)
+{
+ return const_cast<char *>(StripRight((const char *)p,
+ (const char *)end));
+}
+
+/**
+ * Determine the string's length as if it was stripped on the right
+ * side.
+ */
+gcc_pure
+size_t
+StripRight(const char *p, size_t length);
+
+/**
+ * Strip trailing whitespace by null-terminating the string.
+ */
+void
+StripRight(char *p);
+
+/**
+ * Skip whitespace at the beginning and terminate the string after the
+ * last non-whitespace character.
+ */
+char *
+Strip(char *p);
+
+gcc_pure
+bool
+StringStartsWith(const char *haystack, const char *needle);
+
+gcc_pure
+bool
+StringEndsWith(const char *haystack, const char *needle);
+
+/**
+ * Copy a string. If the buffer is too small, then the string is
+ * truncated. This is a safer version of strncpy().
+ *
+ * @param size the size of the destination buffer (including the null
+ * terminator)
+ * @return a pointer to the null terminator
+ */
+gcc_nonnull_all
+char *
+CopyString(char *dest, const char *src, size_t size);
+
/**
* Checks whether a string array contains the specified string.
*
diff --git a/src/util/TextFile.hxx b/src/util/TextFile.hxx
new file mode 100644
index 000000000..3d7d2d0df
--- /dev/null
+++ b/src/util/TextFile.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEXT_FILE_HXX
+#define TEXT_FILE_HXX
+
+#include <string.h>
+
+template<typename B>
+char *
+ReadBufferedLine(B &buffer)
+{
+ auto r = buffer.Read();
+ char *newline = reinterpret_cast<char*>(memchr(r.data, '\n', r.size));
+ if (newline == nullptr)
+ return nullptr;
+
+ buffer.Consume(newline + 1 - r.data);
+
+ if (newline > r.data && newline[-1] == '\r')
+ --newline;
+ *newline = 0;
+ return r.data;
+}
+
+#endif
diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx
index 1c8af23fd..19322b70d 100644
--- a/src/util/Tokenizer.cxx
+++ b/src/util/Tokenizer.cxx
@@ -1,20 +1,30 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2009-2014 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
@@ -24,11 +34,6 @@
#include "Error.hxx"
#include "Domain.hxx"
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
static constexpr Domain tokenizer_domain("tokenizer");
static inline bool
@@ -62,11 +67,11 @@ Tokenizer::NextWord(Error &error)
whitespace or end-of-string */
while (*++input != 0) {
- if (IsWhitespaceOrNull(*input)) {
+ if (IsWhitespaceFast(*input)) {
/* a whitespace: the word ends here */
*input = 0;
/* skip all following spaces, too */
- input = strchug_fast(input + 1);
+ input = StripLeft(input + 1);
break;
}
@@ -107,11 +112,11 @@ Tokenizer::NextUnquoted(Error &error)
whitespace or end-of-string */
while (*++input != 0) {
- if (IsWhitespaceOrNull(*input)) {
+ if (IsWhitespaceFast(*input)) {
/* a whitespace: the word ends here */
*input = 0;
/* skip all following spaces, too */
- input = strchug_fast(input + 1);
+ input = StripLeft(input + 1);
break;
}
@@ -171,7 +176,7 @@ Tokenizer::NextString(Error &error)
line) */
++input;
- if (!IsWhitespaceOrNull(*input)) {
+ if (!IsWhitespaceFast(*input)) {
error.Set(tokenizer_domain,
"Space expected after closing '\"'");
return nullptr;
@@ -180,7 +185,7 @@ Tokenizer::NextString(Error &error)
/* finish the string and return it */
*dest = 0;
- input = strchug_fast(input);
+ input = StripLeft(input);
return word;
}
diff --git a/src/util/Tokenizer.hxx b/src/util/Tokenizer.hxx
index a689dc31d..dc2646589 100644
--- a/src/util/Tokenizer.hxx
+++ b/src/util/Tokenizer.hxx
@@ -1,24 +1,34 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2009-2014 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_TOKENIZER_HXX
-#define MPD_TOKENIZER_HXX
+#ifndef TOKENIZER_HXX
+#define TOKENIZER_HXX
class Error;
diff --git a/src/util/UTF8.cxx b/src/util/UTF8.cxx
new file mode 100644
index 000000000..50ff19e88
--- /dev/null
+++ b/src/util/UTF8.cxx
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2011-2014 Max Kellermann <max@duempel.org>
+ * 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 "UTF8.hxx"
+#include "CharUtil.hxx"
+
+#include <algorithm>
+
+/**
+ * Is this a leading byte that is followed by 1 continuation byte?
+ */
+static constexpr bool
+IsLeading1(unsigned char ch)
+{
+ return (ch & 0xe0) == 0xc0;
+}
+
+static constexpr unsigned char
+MakeLeading1(unsigned char value)
+{
+ return 0xc0 | value;
+}
+
+/**
+ * Is this a leading byte that is followed by 2 continuation byte?
+ */
+static constexpr bool
+IsLeading2(unsigned char ch)
+{
+ return (ch & 0xf0) == 0xe0;
+}
+
+static constexpr unsigned char
+MakeLeading2(unsigned char value)
+{
+ return 0xe0 | value;
+}
+
+/**
+ * Is this a leading byte that is followed by 3 continuation byte?
+ */
+static constexpr bool
+IsLeading3(unsigned char ch)
+{
+ return (ch & 0xf8) == 0xf0;
+}
+
+static constexpr unsigned char
+MakeLeading3(unsigned char value)
+{
+ return 0xf0 | value;
+}
+
+/**
+ * Is this a leading byte that is followed by 4 continuation byte?
+ */
+static constexpr bool
+IsLeading4(unsigned char ch)
+{
+ return (ch & 0xfc) == 0xf8;
+}
+
+static constexpr unsigned char
+MakeLeading4(unsigned char value)
+{
+ return 0xf8 | value;
+}
+
+/**
+ * Is this a leading byte that is followed by 5 continuation byte?
+ */
+static constexpr bool
+IsLeading5(unsigned char ch)
+{
+ return (ch & 0xfe) == 0xfc;
+}
+
+static constexpr unsigned char
+MakeLeading5(unsigned char value)
+{
+ return 0xfc | value;
+}
+
+static constexpr bool
+IsContinuation(unsigned char ch)
+{
+ return (ch & 0xc0) == 0x80;
+}
+
+/**
+ * Generate a continuation byte of the low 6 bit.
+ */
+static constexpr unsigned char
+MakeContinuation(unsigned char value)
+{
+ return 0x80 | (value & 0x3f);
+}
+
+bool
+ValidateUTF8(const char *p)
+{
+ for (; *p != 0; ++p) {
+ unsigned char ch = *p;
+ if (IsASCII(ch))
+ continue;
+
+ if (IsContinuation(ch))
+ /* continuation without a prefix */
+ return false;
+
+ if (IsLeading1(ch)) {
+ /* 1 continuation */
+ if (!IsContinuation(*++p))
+ return false;
+ } else if (IsLeading2(ch)) {
+ /* 2 continuations */
+ if (!IsContinuation(*++p) || !IsContinuation(*++p))
+ return false;
+ } else if (IsLeading3(ch)) {
+ /* 3 continuations */
+ if (!IsContinuation(*++p) || !IsContinuation(*++p) ||
+ !IsContinuation(*++p))
+ return false;
+ } else if (IsLeading4(ch)) {
+ /* 4 continuations */
+ if (!IsContinuation(*++p) || !IsContinuation(*++p) ||
+ !IsContinuation(*++p) || !IsContinuation(*++p))
+ return false;
+ } else if (IsLeading5(ch)) {
+ /* 5 continuations */
+ if (!IsContinuation(*++p) || !IsContinuation(*++p) ||
+ !IsContinuation(*++p) || !IsContinuation(*++p) ||
+ !IsContinuation(*++p))
+ return false;
+ } else
+ return false;
+ }
+
+ return true;
+}
+
+size_t
+SequenceLengthUTF8(char ch)
+{
+ if (IsASCII(ch))
+ return 1;
+ else if (IsLeading1(ch))
+ /* 1 continuation */
+ return 2;
+ else if (IsLeading2(ch))
+ /* 2 continuations */
+ return 3;
+ else if (IsLeading3(ch))
+ /* 3 continuations */
+ return 4;
+ else if (IsLeading4(ch))
+ /* 4 continuations */
+ return 5;
+ else if (IsLeading5(ch))
+ /* 5 continuations */
+ return 6;
+ else
+ /* continuation without a prefix or some other illegal
+ start byte */
+ return 0;
+
+}
+
+template<size_t L>
+struct CheckSequenceUTF8 {
+ gcc_pure
+ bool operator()(const char *p) const {
+ return IsContinuation(*p) && CheckSequenceUTF8<L-1>()(p + 1);
+ }
+};
+
+template<>
+struct CheckSequenceUTF8<0u> {
+ constexpr bool operator()(gcc_unused const char *p) const {
+ return true;
+ }
+};
+
+template<size_t L>
+gcc_pure
+static size_t
+InnerSequenceLengthUTF8(const char *p)
+{
+ return CheckSequenceUTF8<L>()(p)
+ ? L + 1
+ : 0u;
+}
+
+size_t
+SequenceLengthUTF8(const char *p)
+{
+ const unsigned char ch = *p++;
+
+ if (IsASCII(ch))
+ return 1;
+ else if (IsLeading1(ch))
+ /* 1 continuation */
+ return InnerSequenceLengthUTF8<1>(p);
+ else if (IsLeading2(ch))
+ /* 2 continuations */
+ return InnerSequenceLengthUTF8<2>(p);
+ else if (IsLeading3(ch))
+ /* 3 continuations */
+ return InnerSequenceLengthUTF8<3>(p);
+ else if (IsLeading4(ch))
+ /* 4 continuations */
+ return InnerSequenceLengthUTF8<4>(p);
+ else if (IsLeading5(ch))
+ /* 5 continuations */
+ return InnerSequenceLengthUTF8<5>(p);
+ else
+ /* continuation without a prefix or some other illegal
+ start byte */
+ return 0;
+}
+
+static const char *
+FindNonASCIIOrZero(const char *p)
+{
+ while (*p != 0 && IsASCII(*p))
+ ++p;
+ return p;
+}
+
+const char *
+Latin1ToUTF8(const char *gcc_restrict src, char *gcc_restrict buffer,
+ size_t buffer_size)
+{
+ const char *p = FindNonASCIIOrZero(src);
+ if (*p == 0)
+ /* everything is plain ASCII, we don't need to convert anything */
+ return src;
+
+ if ((size_t)(p - src) >= buffer_size)
+ /* buffer too small */
+ return nullptr;
+
+ const char *const end = buffer + buffer_size;
+ char *q = std::copy(src, p, buffer);
+
+ while (*p != 0) {
+ unsigned char ch = *p++;
+
+ if (IsASCII(ch)) {
+ *q++ = ch;
+
+ if (q >= end)
+ /* buffer too small */
+ return nullptr;
+ } else {
+ if (q + 2 >= end)
+ /* buffer too small */
+ return nullptr;
+
+ *q++ = MakeLeading1(ch >> 6);
+ *q++ = MakeContinuation(ch);
+ }
+ }
+
+ *q = 0;
+ return buffer;
+}
+
+char *
+UnicodeToUTF8(unsigned ch, char *q)
+{
+ if (gcc_likely(ch < 0x80)) {
+ *q++ = (char)ch;
+ } else if (gcc_likely(ch < 0x800)) {
+ *q++ = MakeLeading1(ch >> 6);
+ *q++ = MakeContinuation(ch);
+ } else if (ch < 0x10000) {
+ *q++ = MakeLeading2(ch >> 12);
+ *q++ = MakeContinuation(ch >> 6);
+ *q++ = MakeContinuation(ch);
+ } else if (ch < 0x200000) {
+ *q++ = MakeLeading3(ch >> 18);
+ *q++ = MakeContinuation(ch >> 12);
+ *q++ = MakeContinuation(ch >> 6);
+ *q++ = MakeContinuation(ch);
+ } else if (ch < 0x4000000) {
+ *q++ = MakeLeading4(ch >> 24);
+ *q++ = MakeContinuation(ch >> 18);
+ *q++ = MakeContinuation(ch >> 12);
+ *q++ = MakeContinuation(ch >> 6);
+ *q++ = MakeContinuation(ch);
+ } else if (ch < 0x80000000) {
+ *q++ = MakeLeading5(ch >> 30);
+ *q++ = MakeContinuation(ch >> 24);
+ *q++ = MakeContinuation(ch >> 18);
+ *q++ = MakeContinuation(ch >> 12);
+ *q++ = MakeContinuation(ch >> 6);
+ *q++ = MakeContinuation(ch);
+ } else {
+ // error
+ }
+
+ return q;
+}
+
+size_t
+LengthUTF8(const char *p)
+{
+ /* this is a very naive implementation: it does not do any
+ verification, it just counts the bytes that are not a UTF-8
+ continuation */
+
+ size_t n = 0;
+ for (; *p != 0; ++p)
+ if (!IsContinuation(*p))
+ ++n;
+ return n;
+}
diff --git a/src/util/UTF8.hxx b/src/util/UTF8.hxx
new file mode 100644
index 000000000..82d324f3e
--- /dev/null
+++ b/src/util/UTF8.hxx
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011-2014 Max Kellermann <max@duempel.org>
+ * 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.
+ */
+
+#ifndef UTF8_HXX
+#define UTF8_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+/**
+ * Is this a valid UTF-8 string?
+ */
+gcc_pure gcc_nonnull_all
+bool
+ValidateUTF8(const char *p);
+
+/**
+ * @return the number of the sequence beginning with the given
+ * character, or 0 if the character is not a valid start byte
+ */
+gcc_const
+size_t
+SequenceLengthUTF8(char ch);
+
+/**
+ * @return the number of the first sequence in the given string, or 0
+ * if the sequence is malformed
+ */
+gcc_pure
+size_t
+SequenceLengthUTF8(const char *p);
+
+/**
+ * Convert the specified string from ISO-8859-1 to UTF-8.
+ *
+ * @return the UTF-8 version of the source string; may return #src if
+ * there are no non-ASCII characters; returns nullptr if the destination
+ * buffer is too small
+ */
+gcc_pure gcc_nonnull_all
+const char *
+Latin1ToUTF8(const char *src, char *buffer, size_t buffer_size);
+
+/**
+ * Convert the specified Unicode character to UTF-8 and write it to
+ * the buffer. buffer must have a length of at least 6!
+ *
+ * @return a pointer to the buffer plus the added bytes(s)
+ */
+gcc_nonnull_all
+char *
+UnicodeToUTF8(unsigned ch, char *buffer);
+
+/**
+ * Returns the number of characters in the string. This is different
+ * from strlen(), which counts the number of bytes.
+ */
+gcc_pure gcc_nonnull_all
+size_t
+LengthUTF8(const char *p);
+
+#endif
diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx
index 6dd5a42e1..54d0ded77 100644
--- a/src/util/UriUtil.cxx
+++ b/src/util/UriUtil.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,16 @@ bool uri_has_scheme(const char *uri)
return strstr(uri, "://") != nullptr;
}
+std::string
+uri_get_scheme(const char *uri)
+{
+ const char *end = strstr(uri, "://");
+ if (end == nullptr)
+ end = uri;
+
+ return std::string(uri, end);
+}
+
/* suffixes should be ascii only characters */
const char *
uri_get_suffix(const char *uri)
@@ -105,6 +115,8 @@ uri_remove_auth(const char *uri)
auth = uri + 7;
else if (memcmp(uri, "https://", 8) == 0)
auth = uri + 8;
+ else if (memcmp(uri, "ftp://", 6) == 0)
+ auth = uri + 6;
else
/* unrecognized URI */
return std::string();
@@ -145,3 +157,31 @@ uri_is_child_or_same(const char *parent, const char *child)
{
return strcmp(parent, child) == 0 || uri_is_child(parent, child);
}
+
+std::string
+uri_apply_base(const std::string &uri, const std::string &base)
+{
+ if (uri.front() == '/') {
+ /* absolute path: replace the whole URI path in base */
+
+ auto i = base.find("://");
+ if (i == base.npos)
+ /* no scheme: override base completely */
+ return uri;
+
+ /* find the first slash after the host part */
+ i = base.find('/', i + 3);
+ if (i == base.npos)
+ /* there's no URI path - simply append uri */
+ i = base.length();
+
+ return base.substr(0, i) + uri;
+ }
+
+ std::string out(base);
+ if (out.back() != '/')
+ out.push_back('/');
+
+ out += uri;
+ return out;
+}
diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx
index 1c6bce3ff..d478d5b92 100644
--- a/src/util/UriUtil.hxx
+++ b/src/util/UriUtil.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -31,6 +31,13 @@
gcc_pure
bool uri_has_scheme(const char *uri);
+/**
+ * Returns the scheme name of the specified URI, or an empty string.
+ */
+gcc_pure
+std::string
+uri_get_scheme(const char *uri);
+
gcc_pure
const char *
uri_get_suffix(const char *uri);
@@ -81,4 +88,12 @@ gcc_pure gcc_nonnull_all
bool
uri_is_child_or_same(const char *parent, const char *child);
+/**
+ * Translate the given URI in the context of #base. For example,
+ * uri_apply_base("foo", "http://bar/a/")=="http://bar/a/foo".
+ */
+gcc_pure
+std::string
+uri_apply_base(const std::string &uri, const std::string &base);
+
#endif
diff --git a/src/util/VarSize.hxx b/src/util/VarSize.hxx
new file mode 100644
index 000000000..04f1bf580
--- /dev/null
+++ b/src/util/VarSize.hxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MPD_VAR_SIZE_HXX
+#define MPD_VAR_SIZE_HXX
+
+#include "Alloc.hxx"
+#include "Compiler.h"
+
+#include <type_traits>
+#include <utility>
+#include <new>
+
+/**
+ * Allocate and construct a variable-size object. That is useful for
+ * example when you want to store a variable-length string as the last
+ * attribute without the overhead of a second allocation.
+ *
+ * @param T a struct/class with a variable-size last attribute
+ * @param declared_tail_size the declared size of the last element in
+ * #T
+ * @param real_tail_size the real required size of the last element in
+ * #T
+ */
+template<class T, typename... Args>
+gcc_malloc
+T *
+NewVarSize(size_t declared_tail_size, size_t real_tail_size, Args&&... args)
+{
+ static_assert(std::is_standard_layout<T>::value,
+ "Not standard-layout");
+
+ /* determine the total size of this instance */
+ size_t size = sizeof(T) - declared_tail_size + real_tail_size;
+
+ /* allocate memory */
+ T *instance = (T *)xalloc(size);
+
+ /* call the constructor */
+ new(instance) T(std::forward<Args>(args)...);
+
+ return instance;
+}
+
+template<typename T>
+gcc_nonnull_all
+void
+DeleteVarSize(T *instance)
+{
+ /* call the destructor */
+ instance->T::~T();
+
+ /* free memory */
+ free(instance);
+}
+
+#endif
diff --git a/src/util/WritableBuffer.hxx b/src/util/WritableBuffer.hxx
index 4e529cfad..f9e6d4a96 100644
--- a/src/util/WritableBuffer.hxx
+++ b/src/util/WritableBuffer.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Max Kellermann <max@duempel.org>
+ * Copyright (C) 2013-2014 Max Kellermann <max@duempel.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -32,7 +32,45 @@
#include "Compiler.h"
-#include <stddef.h>
+#include <cstddef>
+
+#ifndef NDEBUG
+#include <assert.h>
+#endif
+
+template<typename T>
+struct WritableBuffer;
+
+template<>
+struct WritableBuffer<void> {
+ typedef size_t size_type;
+ typedef void *pointer_type;
+ typedef const void *const_pointer_type;
+ typedef pointer_type iterator;
+ typedef const_pointer_type const_iterator;
+
+ pointer_type data;
+ size_type size;
+
+ WritableBuffer() = default;
+
+ constexpr WritableBuffer(std::nullptr_t):data(nullptr), size(0) {}
+
+ constexpr WritableBuffer(pointer_type _data, size_type _size)
+ :data(_data), size(_size) {}
+
+ constexpr static WritableBuffer Null() {
+ return { nullptr, 0 };
+ }
+
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
+
+ constexpr bool IsEmpty() const {
+ return size == 0;
+ }
+};
/**
* A reference to a memory area that is writable.
@@ -41,47 +79,144 @@
*/
template<typename T>
struct WritableBuffer {
- typedef size_t size_type;
- typedef T *pointer_type;
- typedef const T *const_pointer_type;
- typedef pointer_type iterator;
- typedef const_pointer_type const_iterator;
+ typedef size_t size_type;
+ typedef T &reference_type;
+ typedef const T &const_reference_type;
+ typedef T *pointer_type;
+ typedef const T *const_pointer_type;
+ typedef pointer_type iterator;
+ typedef const_pointer_type const_iterator;
- pointer_type data;
- size_type size;
+ pointer_type data;
+ size_type size;
- WritableBuffer() = default;
+ WritableBuffer() = default;
- constexpr WritableBuffer(pointer_type _data, size_type _size)
- :data(_data), size(_size) {}
+ constexpr WritableBuffer(std::nullptr_t):data(nullptr), size(0) {}
- constexpr static WritableBuffer Null() {
- return { nullptr, 0 };
- }
+ constexpr WritableBuffer(pointer_type _data, size_type _size)
+ :data(_data), size(_size) {}
+
+ constexpr static WritableBuffer Null() {
+ return { nullptr, 0 };
+ }
+
+ /**
+ * Cast a WritableBuffer<void> to a WritableBuffer<T>. A "void"
+ * buffer records its size in bytes, and when casting to "T",
+ * the assertion below ensures that the size is a multiple of
+ * sizeof(T).
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ static WritableBuffer<T> FromVoid(WritableBuffer<void> other) {
+ static_assert(sizeof(T) > 0, "Empty base type");
+#ifndef NDEBUG
+ assert(other.size % sizeof(T) == 0);
+#endif
+ return WritableBuffer<T>(pointer_type(other.data),
+ other.size / sizeof(T));
+ }
- constexpr bool IsNull() const {
- return data == nullptr;
- }
+ constexpr WritableBuffer<void> ToVoid() const {
+ static_assert(sizeof(T) > 0, "Empty base type");
+ return WritableBuffer<void>(data, size * sizeof(T));
+ }
+
+ constexpr bool IsNull() const {
+ return data == nullptr;
+ }
+
+ constexpr bool IsEmpty() const {
+ return size == 0;
+ }
+
+ constexpr iterator begin() const {
+ return data;
+ }
+
+ constexpr iterator end() const {
+ return data + size;
+ }
+
+ constexpr const_iterator cbegin() const {
+ return data;
+ }
+
+ constexpr const_iterator cend() const {
+ return data + size;
+ }
+
+#ifdef NDEBUG
+ constexpr
+#endif
+ reference_type operator[](size_type i) const {
+#ifndef NDEBUG
+ assert(i < size);
+#endif
+
+ return data[i];
+ }
+
+ /**
+ * Returns a reference to the first element. Buffer must not
+ * be empty.
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ reference_type front() const {
+#ifndef NDEBUG
+ assert(!IsEmpty());
+#endif
+ return data[0];
+ }
+
+ /**
+ * Returns a reference to the last element. Buffer must not
+ * be empty.
+ */
+#ifdef NDEBUG
+ constexpr
+#endif
+ reference_type back() const {
+#ifndef NDEBUG
+ assert(!IsEmpty());
+#endif
+ return data[size - 1];
+ }
- constexpr bool IsEmpty() const {
- return size == 0;
- }
+ /**
+ * Remove the first element (by moving the head pointer, does
+ * not actually modify the buffer). Buffer must not be empty.
+ */
+ void pop_front() {
+ assert(!IsEmpty());
- constexpr iterator begin() const {
- return data;
- }
+ ++data;
+ --size;
+ }
- constexpr iterator end() const {
- return data + size;
- }
+ /**
+ * Remove the last element (by moving the tail pointer, does
+ * not actually modify the buffer). Buffer must not be empty.
+ */
+ void pop_back() {
+ assert(!IsEmpty());
- constexpr const_iterator cbegin() const {
- return data;
- }
+ --size;
+ }
- constexpr const_iterator cend() const {
- return data + size;
- }
+ /**
+ * Remove the first element and return a reference to it.
+ * Buffer must not be empty.
+ */
+ reference_type shift() {
+ reference_type result = front();
+ pop_front();
+ return result;
+ }
};
#endif
diff --git a/src/util/bit_reverse.c b/src/util/bit_reverse.c
index ba8a23ef1..9226c4261 100644
--- a/src/util/bit_reverse.c
+++ b/src/util/bit_reverse.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h
index a02f01b3f..b39b02e92 100644
--- a/src/util/bit_reverse.h
+++ b/src/util/bit_reverse.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/util/fifo_buffer.c b/src/util/fifo_buffer.c
deleted file mode 100644
index 162ddf946..000000000
--- a/src/util/fifo_buffer.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "config.h"
-#include "fifo_buffer.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-struct fifo_buffer {
- size_t size, start, end;
- unsigned char buffer[sizeof(size_t)];
-};
-
-struct fifo_buffer *
-fifo_buffer_new(size_t size)
-{
- struct fifo_buffer *buffer;
-
- assert(size > 0);
-
- buffer = (struct fifo_buffer *)g_malloc(sizeof(*buffer) -
- sizeof(buffer->buffer) + size);
-
- buffer->size = size;
- buffer->start = 0;
- buffer->end = 0;
-
- return buffer;
-}
-
-void
-fifo_buffer_init(struct fifo_buffer *buffer, size_t size)
-{
- buffer->size = size - (sizeof(*buffer) - sizeof(buffer->buffer));
- buffer->start = 0;
- buffer->end = 0;
-}
-
-static void
-fifo_buffer_move(struct fifo_buffer *buffer);
-
-struct fifo_buffer *
-fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size)
-{
- if (buffer == NULL)
- return new_size > 0
- ? fifo_buffer_new(new_size)
- : NULL;
-
- /* existing data must fit in new size */
- assert(new_size >= buffer->end - buffer->start);
-
- if (new_size == 0) {
- fifo_buffer_free(buffer);
- return NULL;
- }
-
- /* compress the buffer when we're shrinking and the tail of
- the buffer would exceed the new size */
- if (buffer->end > new_size)
- fifo_buffer_move(buffer);
-
- /* existing data must fit in new size: second check */
- assert(buffer->end <= new_size);
-
- buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) +
- new_size);
- buffer->size = new_size;
- return buffer;
-}
-
-void
-fifo_buffer_free(struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- g_free(buffer);
-}
-
-size_t
-fifo_buffer_capacity(const struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- return buffer->size;
-}
-
-size_t
-fifo_buffer_available(const struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- return buffer->end - buffer->start;
-}
-
-void
-fifo_buffer_clear(struct fifo_buffer *buffer)
-{
- assert(buffer != NULL);
-
- buffer->start = 0;
- buffer->end = 0;
-}
-
-const void *
-fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r)
-{
- assert(buffer != NULL);
- assert(buffer->end >= buffer->start);
- assert(length_r != NULL);
-
- if (buffer->start == buffer->end)
- /* the buffer is empty */
- return NULL;
-
- *length_r = buffer->end - buffer->start;
- return buffer->buffer + buffer->start;
-}
-
-void
-fifo_buffer_consume(struct fifo_buffer *buffer, size_t length)
-{
- assert(buffer != NULL);
- assert(buffer->end >= buffer->start);
- assert(buffer->start + length <= buffer->end);
-
- buffer->start += length;
-}
-
-/**
- * Move data to the beginning of the buffer, to make room at the end.
- */
-static void
-fifo_buffer_move(struct fifo_buffer *buffer)
-{
- if (buffer->start == 0)
- return;
-
- if (buffer->end > buffer->start)
- memmove(buffer->buffer,
- buffer->buffer + buffer->start,
- buffer->end - buffer->start);
-
- buffer->end -= buffer->start;
- buffer->start = 0;
-}
-
-void *
-fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r)
-{
- assert(buffer != NULL);
- assert(buffer->end <= buffer->size);
- assert(max_length_r != NULL);
-
- if (buffer->end == buffer->size) {
- fifo_buffer_move(buffer);
- if (buffer->end == buffer->size)
- return NULL;
- } else if (buffer->start > 0 && buffer->start == buffer->end) {
- buffer->start = 0;
- buffer->end = 0;
- }
-
- *max_length_r = buffer->size - buffer->end;
- return buffer->buffer + buffer->end;
-}
-
-void
-fifo_buffer_append(struct fifo_buffer *buffer, size_t length)
-{
- assert(buffer != NULL);
- assert(buffer->end >= buffer->start);
- assert(buffer->end + length <= buffer->size);
-
- buffer->end += length;
-}
-
-bool
-fifo_buffer_is_empty(struct fifo_buffer *buffer)
-{
- return buffer->start == buffer->end;
-}
-
-bool
-fifo_buffer_is_full(struct fifo_buffer *buffer)
-{
- return buffer->start == 0 && buffer->end == buffer->size;
-}
diff --git a/src/util/fifo_buffer.h b/src/util/fifo_buffer.h
deleted file mode 100644
index ccea97d86..000000000
--- a/src/util/fifo_buffer.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** \file
- *
- * This is a general purpose FIFO buffer library. You may append data
- * at the end, while another instance reads data from the beginning.
- * It is optimized for zero-copy usage: you get pointers to the real
- * buffer, where you may operate on.
- *
- * This library is not thread safe.
- */
-
-#ifndef MPD_FIFO_BUFFER_H
-#define MPD_FIFO_BUFFER_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-struct fifo_buffer;
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Creates a new #fifo_buffer object. Free this object with
- * fifo_buffer_free().
- *
- * @param size the size of the buffer in bytes
- * @return the new #fifo_buffer object
- */
-struct fifo_buffer *
-fifo_buffer_new(size_t size);
-
-void
-fifo_buffer_init(struct fifo_buffer *buffer, size_t size);
-
-/**
- * Change the capacity of the #fifo_buffer, while preserving existing
- * data.
- *
- * @param buffer the old buffer, may be NULL
- * @param new_size the requested new size of the #fifo_buffer; must
- * not be smaller than the data which is stored in the old buffer
- * @return the new buffer, may be NULL if the requested new size is 0
- */
-struct fifo_buffer *
-fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size);
-
-/**
- * Frees the resources consumed by this #fifo_buffer object.
- */
-void
-fifo_buffer_free(struct fifo_buffer *buffer);
-
-/**
- * Return the capacity of the buffer, i.e. the size that was passed to
- * fifo_buffer_new().
- */
-size_t
-fifo_buffer_capacity(const struct fifo_buffer *buffer);
-
-/**
- * Return the number of bytes currently stored in the buffer.
- */
-size_t
-fifo_buffer_available(const struct fifo_buffer *buffer);
-
-/**
- * Clears all data currently in this #fifo_buffer object. This does
- * not overwrite the actuall buffer; it just resets the internal
- * pointers.
- */
-void
-fifo_buffer_clear(struct fifo_buffer *buffer);
-
-/**
- * Reads from the beginning of the buffer. To remove consumed data
- * from the buffer, call fifo_buffer_consume().
- *
- * @param buffer the #fifo_buffer object
- * @param length_r the maximum amount to read is returned here
- * @return a pointer to the beginning of the buffer, or NULL if the
- * buffer is empty
- */
-const void *
-fifo_buffer_read(const struct fifo_buffer *buffer, size_t *length_r);
-
-/**
- * Marks data at the beginning of the buffer as "consumed".
- *
- * @param buffer the #fifo_buffer object
- * @param length the number of bytes which were consumed
- */
-void
-fifo_buffer_consume(struct fifo_buffer *buffer, size_t length);
-
-/**
- * Prepares writing to the buffer. This returns a buffer which you
- * can write to. To commit the write operation, call
- * fifo_buffer_append().
- *
- * @param buffer the #fifo_buffer object
- * @param max_length_r the maximum amount to write is returned here
- * @return a pointer to the end of the buffer, or NULL if the buffer
- * is already full
- */
-void *
-fifo_buffer_write(struct fifo_buffer *buffer, size_t *max_length_r);
-
-/**
- * Commits the write operation initiated by fifo_buffer_write().
- *
- * @param buffer the #fifo_buffer object
- * @param length the number of bytes which were written
- */
-void
-fifo_buffer_append(struct fifo_buffer *buffer, size_t length);
-
-/**
- * Checks if the buffer is empty.
- */
-bool
-fifo_buffer_is_empty(struct fifo_buffer *buffer);
-
-/**
- * Checks if the buffer is full.
- */
-bool
-fifo_buffer_is_full(struct fifo_buffer *buffer);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/src/util/growing_fifo.c b/src/util/growing_fifo.c
deleted file mode 100644
index 88431f60e..000000000
--- a/src/util/growing_fifo.c
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "growing_fifo.h"
-#include "fifo_buffer.h"
-
-#include <assert.h>
-#include <string.h>
-
-/**
- * Align buffer sizes at 8 kB boundaries. Must be a power of two.
- */
-static const size_t GROWING_FIFO_ALIGN = 8192;
-
-/**
- * Align the specified size to the next #GROWING_FIFO_ALIGN boundary.
- */
-static size_t
-align(size_t size)
-{
- return ((size - 1) | (GROWING_FIFO_ALIGN - 1)) + 1;
-}
-
-struct fifo_buffer *
-growing_fifo_new(void)
-{
- return fifo_buffer_new(GROWING_FIFO_ALIGN);
-}
-
-void *
-growing_fifo_write(struct fifo_buffer **buffer_p, size_t length)
-{
- assert(buffer_p != NULL);
-
- struct fifo_buffer *buffer = *buffer_p;
- assert(buffer != NULL);
-
- size_t max_length;
- void *p = fifo_buffer_write(buffer, &max_length);
- if (p != NULL && max_length >= length)
- return p;
-
- /* grow */
- size_t new_size = fifo_buffer_available(buffer) + length;
- assert(new_size > fifo_buffer_capacity(buffer));
- *buffer_p = buffer = fifo_buffer_realloc(buffer, align(new_size));
-
- /* try again */
- p = fifo_buffer_write(buffer, &max_length);
- assert(p != NULL);
- assert(max_length >= length);
-
- return p;
-}
-
-void
-growing_fifo_append(struct fifo_buffer **buffer_p,
- const void *data, size_t length)
-{
- void *p = growing_fifo_write(buffer_p, length);
- memcpy(p, data, length);
- fifo_buffer_append(*buffer_p, length);
-}
diff --git a/src/util/growing_fifo.h b/src/util/growing_fifo.h
deleted file mode 100644
index 723c3b3ff..000000000
--- a/src/util/growing_fifo.h
+++ /dev/null
@@ -1,73 +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
- *
- * Helper functions for our FIFO buffer library (fifo_buffer.h) that
- * allows growing the buffer on demand.
- *
- * This library is not thread safe.
- */
-
-#ifndef MPD_GROWING_FIFO_H
-#define MPD_GROWING_FIFO_H
-
-#include <stddef.h>
-
-struct fifo_buffer;
-
-/**
- * Allocate a new #fifo_buffer with the default size.
- */
-struct fifo_buffer *
-growing_fifo_new(void);
-
-/**
- * Prepares writing to the buffer, see fifo_buffer_write() for
- * details. The difference is that this function will automatically
- * grow the buffer if it is too small.
- *
- * The caller is responsible for limiting the capacity of the buffer.
- *
- * @param length the number of bytes that will be written
- * @return a pointer to the end of the buffer (will not be NULL)
- */
-void *
-growing_fifo_write(struct fifo_buffer **buffer_p, size_t length);
-
-/**
- * A helper function that combines growing_fifo_write(), memcpy(),
- * fifo_buffer_append().
- */
-void
-growing_fifo_append(struct fifo_buffer **buffer_p,
- const void *data, size_t length);
-
-#endif
diff --git a/src/util/list.h b/src/util/list.h
deleted file mode 100644
index 73d99befa..000000000
--- a/src/util/list.h
+++ /dev/null
@@ -1,607 +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.
- */
-
-/*
- * This code was imported from the Linux kernel.
- *
- */
-
-#ifndef _LINUX_LIST_H
-#define _LINUX_LIST_H
-
-#ifdef __clang__
-/* allow typeof() */
-#pragma GCC diagnostic ignored "-Wlanguage-extension-token"
-#endif
-
-/**
- * container_of - cast a member of a structure out to the containing structure
- * @ptr: the pointer to the member.
- * @type: the type of the container struct this is embedded in.
- * @member: the name of the member within the struct.
- *
- */
-#define container_of(ptr, 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 ((struct list_head *)(void *) 0x00100100)
-#define LIST_POISON2 ((struct list_head *)(void *) 0x00200200)
-
-/*
- * Simple doubly linked list implementation.
- *
- * Some of the internal functions ("__xxx") are useful when
- * manipulating whole lists rather than single entries, as
- * sometimes we already know the next/prev entries and we can
- * generate better code by using them directly rather than
- * using the generic single-entry routines.
- */
-
-struct list_head {
- struct list_head *next, *prev;
-};
-
-#define LIST_HEAD_INIT(name) { &(name), &(name) }
-
-#define LIST_HEAD(name) \
- struct list_head name = LIST_HEAD_INIT(name)
-
-static inline void INIT_LIST_HEAD(struct list_head *list)
-{
- list->next = list;
- list->prev = list;
-}
-
-/*
- * Insert a new entry between two known consecutive entries.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-#ifndef CONFIG_DEBUG_LIST
-static inline void __list_add(struct list_head *new_item,
- struct list_head *prev,
- struct list_head *next)
-{
- 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_item,
- struct list_head *prev,
- struct list_head *next);
-#endif
-
-/**
- * list_add - add a new entry
- * @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_item, struct list_head *head)
-{
- __list_add(new_item, head, head->next);
-}
-
-
-/**
- * list_add_tail - add a new entry
- * @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_item, struct list_head *head)
-{
- __list_add(new_item, head->prev, head);
-}
-
-/*
- * Delete a list entry by making the prev/next entries
- * point to each other.
- *
- * This is only for internal list manipulation where we know
- * the prev/next entries already!
- */
-static inline void __list_del(struct list_head * prev, struct list_head * next)
-{
- next->prev = prev;
- prev->next = next;
-}
-
-/**
- * list_del - deletes entry from list.
- * @entry: the element to delete from the list.
- * Note: list_empty() on entry does not return true after this, the entry is
- * in an undefined state.
- */
-#ifndef CONFIG_DEBUG_LIST
-static inline void __list_del_entry(struct list_head *entry)
-{
- __list_del(entry->prev, entry->next);
-}
-
-static inline void list_del(struct list_head *entry)
-{
- __list_del(entry->prev, entry->next);
- entry->next = LIST_POISON1;
- entry->prev = LIST_POISON2;
-}
-#else
-extern void __list_del_entry(struct list_head *entry);
-extern void list_del(struct list_head *entry);
-#endif
-
-/**
- * list_replace - replace old entry by new one
- * @old : the element to be replaced
- * @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_item)
-{
- 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_item)
-{
- list_replace(old, new_item);
- INIT_LIST_HEAD(old);
-}
-
-/**
- * list_del_init - deletes entry from list and reinitialize it.
- * @entry: the element to delete from the list.
- */
-static inline void list_del_init(struct list_head *entry)
-{
- __list_del_entry(entry);
- INIT_LIST_HEAD(entry);
-}
-
-/**
- * list_move - delete from one list and add as another's head
- * @list: the entry to move
- * @head: the head that will precede our entry
- */
-static inline void list_move(struct list_head *list, struct list_head *head)
-{
- __list_del_entry(list);
- list_add(list, head);
-}
-
-/**
- * list_move_tail - delete from one list and add as another's tail
- * @list: the entry to move
- * @head: the head that will follow our entry
- */
-static inline void list_move_tail(struct list_head *list,
- struct list_head *head)
-{
- __list_del_entry(list);
- list_add_tail(list, head);
-}
-
-/**
- * list_is_last - tests whether @list is the last entry in list @head
- * @list: the entry to test
- * @head: the head of the list
- */
-static inline int list_is_last(const struct list_head *list,
- const struct list_head *head)
-{
- return list->next == head;
-}
-
-/**
- * list_empty - tests whether a list is empty
- * @head: the list to test.
- */
-static inline int list_empty(const struct list_head *head)
-{
- return head->next == head;
-}
-
-/**
- * list_empty_careful - tests whether a list is empty and not being modified
- * @head: the list to test
- *
- * Description:
- * tests whether a list is empty _and_ checks that no other CPU might be
- * in the process of modifying either member (next or prev)
- *
- * NOTE: using list_empty_careful() without synchronization
- * can only be safe if the only activity that can happen
- * to the list entry is list_del_init(). Eg. it cannot be used
- * if another CPU could re-list_add() it.
- */
-static inline int list_empty_careful(const struct list_head *head)
-{
- struct list_head *next = head->next;
- return (next == head) && (next == head->prev);
-}
-
-/**
- * list_rotate_left - rotate the list to the left
- * @head: the head of the list
- */
-static inline void list_rotate_left(struct list_head *head)
-{
- struct list_head *first;
-
- if (!list_empty(head)) {
- first = head->next;
- list_move_tail(first, head);
- }
-}
-
-/**
- * list_is_singular - tests whether a list has just one entry.
- * @head: the list to test.
- */
-static inline int list_is_singular(const struct list_head *head)
-{
- return !list_empty(head) && (head->next == head->prev);
-}
-
-static inline void __list_cut_position(struct list_head *list,
- struct list_head *head, struct list_head *entry)
-{
- struct list_head *new_first = entry->next;
- list->next = head->next;
- list->next->prev = list;
- list->prev = entry;
- entry->next = list;
- head->next = new_first;
- new_first->prev = head;
-}
-
-/**
- * list_cut_position - cut a list into two
- * @list: a new list to add all removed entries
- * @head: a list with entries
- * @entry: an entry within head, could be the head itself
- * and if so we won't cut the list
- *
- * This helper moves the initial part of @head, up to and
- * including @entry, from @head to @list. You should
- * pass on @entry an element you know is on @head. @list
- * should be an empty list or a list you do not care about
- * losing its data.
- *
- */
-static inline void list_cut_position(struct list_head *list,
- struct list_head *head, struct list_head *entry)
-{
- if (list_empty(head))
- return;
- if (list_is_singular(head) &&
- (head->next != entry && head != entry))
- return;
- if (entry == head)
- INIT_LIST_HEAD(list);
- else
- __list_cut_position(list, head, entry);
-}
-
-static inline void __list_splice(const struct list_head *list,
- struct list_head *prev,
- struct list_head *next)
-{
- struct list_head *first = list->next;
- struct list_head *last = list->prev;
-
- first->prev = prev;
- prev->next = first;
-
- last->next = next;
- next->prev = last;
-}
-
-/**
- * list_splice - join two lists, this is designed for stacks
- * @list: the new list to add.
- * @head: the place to add it in the first list.
- */
-static inline void list_splice(const struct list_head *list,
- struct list_head *head)
-{
- if (!list_empty(list))
- __list_splice(list, head, head->next);
-}
-
-/**
- * list_splice_tail - join two lists, each list being a queue
- * @list: the new list to add.
- * @head: the place to add it in the first list.
- */
-static inline void list_splice_tail(struct list_head *list,
- struct list_head *head)
-{
- if (!list_empty(list))
- __list_splice(list, head->prev, head);
-}
-
-/**
- * list_splice_init - join two lists and reinitialise the emptied list.
- * @list: the new list to add.
- * @head: the place to add it in the first list.
- *
- * The list at @list is reinitialised
- */
-static inline void list_splice_init(struct list_head *list,
- struct list_head *head)
-{
- if (!list_empty(list)) {
- __list_splice(list, head, head->next);
- INIT_LIST_HEAD(list);
- }
-}
-
-/**
- * list_splice_tail_init - join two lists and reinitialise the emptied list
- * @list: the new list to add.
- * @head: the place to add it in the first list.
- *
- * Each of the lists is a queue.
- * The list at @list is reinitialised
- */
-static inline void list_splice_tail_init(struct list_head *list,
- struct list_head *head)
-{
- if (!list_empty(list)) {
- __list_splice(list, head->prev, head);
- INIT_LIST_HEAD(list);
- }
-}
-
-/**
- * list_entry - get the struct for this entry
- * @ptr: the &struct list_head pointer.
- * @type: the type of the struct this is embedded in.
- * @member: the name of the list_struct within the struct.
- */
-#define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
-
-/**
- * list_first_entry - get the first element from a list
- * @ptr: the list head to take the element from.
- * @type: the type of the struct this is embedded in.
- * @member: the name of the list_struct within the struct.
- *
- * Note, that list is expected to be not empty.
- */
-#define list_first_entry(ptr, type, member) \
- list_entry((ptr)->next, type, member)
-
-/**
- * list_for_each - iterate over a list
- * @pos: the &struct list_head to use as a loop cursor.
- * @head: the head for your list.
- */
-#define list_for_each(pos, head) \
- for (pos = (head)->next; pos != (head); pos = pos->next)
-
-/**
- * __list_for_each - iterate over a list
- * @pos: the &struct list_head to use as a loop cursor.
- * @head: the head for your list.
- *
- * This variant doesn't differ from list_for_each() any more.
- * We don't do prefetching in either case.
- */
-#define __list_for_each(pos, head) \
- for (pos = (head)->next; pos != (head); pos = pos->next)
-
-/**
- * list_for_each_prev - iterate over a list backwards
- * @pos: the &struct list_head to use as a loop cursor.
- * @head: the head for your list.
- */
-#define list_for_each_prev(pos, head) \
- for (pos = (head)->prev; pos != (head); pos = pos->prev)
-
-/**
- * list_for_each_safe - iterate over a list safe against removal of list entry
- * @pos: the &struct list_head to use as a loop cursor.
- * @n: another &struct list_head to use as temporary storage
- * @head: the head for your list.
- */
-#define list_for_each_safe(pos, n, head) \
- for (pos = (head)->next, n = pos->next; pos != (head); \
- pos = n, n = pos->next)
-
-/**
- * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
- * @pos: the &struct list_head to use as a loop cursor.
- * @n: another &struct list_head to use as temporary storage
- * @head: the head for your list.
- */
-#define list_for_each_prev_safe(pos, n, head) \
- for (pos = (head)->prev, n = pos->prev; \
- pos != (head); \
- pos = n, n = pos->prev)
-
-/**
- * list_for_each_entry - iterate over list of given type
- * @pos: the type * to use as a loop cursor.
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- */
-#define list_for_each_entry(pos, head, member) \
- for (pos = list_entry((head)->next, typeof(*pos), member); \
- &pos->member != (head); \
- pos = list_entry(pos->member.next, typeof(*pos), member))
-
-/**
- * list_for_each_entry_reverse - iterate backwards over list of given type.
- * @pos: the type * to use as a loop cursor.
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- */
-#define list_for_each_entry_reverse(pos, head, member) \
- for (pos = list_entry((head)->prev, typeof(*pos), member); \
- &pos->member != (head); \
- pos = list_entry(pos->member.prev, typeof(*pos), member))
-
-/**
- * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
- * @pos: the type * to use as a start point
- * @head: the head of the list
- * @member: the name of the list_struct within the struct.
- *
- * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
- */
-#define list_prepare_entry(pos, head, member) \
- ((pos) ? : list_entry(head, typeof(*pos), member))
-
-/**
- * list_for_each_entry_continue - continue iteration over list of given type
- * @pos: the type * to use as a loop cursor.
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- *
- * Continue to iterate over list of given type, continuing after
- * the current position.
- */
-#define list_for_each_entry_continue(pos, head, member) \
- for (pos = list_entry(pos->member.next, typeof(*pos), member); \
- &pos->member != (head); \
- pos = list_entry(pos->member.next, typeof(*pos), member))
-
-/**
- * list_for_each_entry_continue_reverse - iterate backwards from the given point
- * @pos: the type * to use as a loop cursor.
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- *
- * Start to iterate over list of given type backwards, continuing after
- * the current position.
- */
-#define list_for_each_entry_continue_reverse(pos, head, member) \
- for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
- &pos->member != (head); \
- pos = list_entry(pos->member.prev, typeof(*pos), member))
-
-/**
- * list_for_each_entry_from - iterate over list of given type from the current point
- * @pos: the type * to use as a loop cursor.
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- *
- * Iterate over list of given type, continuing from current position.
- */
-#define list_for_each_entry_from(pos, head, member) \
- for (; &pos->member != (head); \
- pos = list_entry(pos->member.next, typeof(*pos), member))
-
-/**
- * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
- * @pos: the type * to use as a loop cursor.
- * @n: another type * to use as temporary storage
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- */
-#define list_for_each_entry_safe(pos, n, head, member) \
- for (pos = list_entry((head)->next, typeof(*pos), member), \
- n = list_entry(pos->member.next, typeof(*pos), member); \
- &pos->member != (head); \
- pos = n, n = list_entry(n->member.next, typeof(*n), member))
-
-/**
- * list_for_each_entry_safe_continue - continue list iteration safe against removal
- * @pos: the type * to use as a loop cursor.
- * @n: another type * to use as temporary storage
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- *
- * Iterate over list of given type, continuing after current point,
- * safe against removal of list entry.
- */
-#define list_for_each_entry_safe_continue(pos, n, head, member) \
- for (pos = list_entry(pos->member.next, typeof(*pos), member), \
- n = list_entry(pos->member.next, typeof(*pos), member); \
- &pos->member != (head); \
- pos = n, n = list_entry(n->member.next, typeof(*n), member))
-
-/**
- * list_for_each_entry_safe_from - iterate over list from current point safe against removal
- * @pos: the type * to use as a loop cursor.
- * @n: another type * to use as temporary storage
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- *
- * Iterate over list of given type from current point, safe against
- * removal of list entry.
- */
-#define list_for_each_entry_safe_from(pos, n, head, member) \
- for (n = list_entry(pos->member.next, typeof(*pos), member); \
- &pos->member != (head); \
- pos = n, n = list_entry(n->member.next, typeof(*n), member))
-
-/**
- * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
- * @pos: the type * to use as a loop cursor.
- * @n: another type * to use as temporary storage
- * @head: the head for your list.
- * @member: the name of the list_struct within the struct.
- *
- * Iterate backwards over list of given type, safe against removal
- * of list entry.
- */
-#define list_for_each_entry_safe_reverse(pos, n, head, member) \
- for (pos = list_entry((head)->prev, typeof(*pos), member), \
- n = list_entry(pos->member.prev, typeof(*pos), member); \
- &pos->member != (head); \
- pos = n, n = list_entry(n->member.prev, typeof(*n), member))
-
-/**
- * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
- * @pos: the loop cursor used in the list_for_each_entry_safe loop
- * @n: temporary storage used in list_for_each_entry_safe
- * @member: the name of the list_struct within the struct.
- *
- * list_safe_reset_next is not safe to use in general if the list may be
- * modified concurrently (eg. the lock is dropped in the loop body). An
- * exception to this is if the cursor element (pos) is pinned in the list,
- * and list_safe_reset_next is called after re-taking the lock and before
- * completing the current iteration of the loop body.
- */
-#define list_safe_reset_next(pos, n, member) \
- n = list_entry(pos->member.next, typeof(*pos), member)
-
-#endif
diff --git a/src/util/list_sort.c b/src/util/list_sort.c
deleted file mode 100644
index 8534f3360..000000000
--- a/src/util/list_sort.c
+++ /dev/null
@@ -1,162 +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.
- */
-
-/*
- * This code was imported from the Linux kernel.
- *
- */
-
-#include "list_sort.h"
-#include "list.h"
-#include "Macros.hxx"
-#include "Compiler.h"
-
-#include <string.h>
-
-#define unlikely gcc_unlikely
-
-#define MAX_LIST_LENGTH_BITS 20
-
-/*
- * Returns a list organized in an intermediate format suited
- * to chaining of merge() calls: null-terminated, no reserved or
- * sentinel head node, "prev" links not maintained.
- */
-static struct list_head *merge(void *priv,
- int (*cmp)(void *priv, struct list_head *a,
- struct list_head *b),
- struct list_head *a, struct list_head *b)
-{
- struct list_head head, *tail = &head;
-
- while (a && b) {
- /* if equal, take 'a' -- important for sort stability */
- if ((*cmp)(priv, a, b) <= 0) {
- tail->next = a;
- a = a->next;
- } else {
- tail->next = b;
- b = b->next;
- }
- tail = tail->next;
- }
- tail->next = a?a:b;
- return head.next;
-}
-
-/*
- * Combine final list merge with restoration of standard doubly-linked
- * list structure. This approach duplicates code from merge(), but
- * runs faster than the tidier alternatives of either a separate final
- * prev-link restoration pass, or maintaining the prev links
- * throughout.
- */
-static void merge_and_restore_back_links(void *priv,
- int (*cmp)(void *priv, struct list_head *a,
- struct list_head *b),
- struct list_head *head,
- struct list_head *a, struct list_head *b)
-{
- struct list_head *tail = head;
-
- while (a && b) {
- /* if equal, take 'a' -- important for sort stability */
- if ((*cmp)(priv, a, b) <= 0) {
- tail->next = a;
- a->prev = tail;
- a = a->next;
- } else {
- tail->next = b;
- b->prev = tail;
- b = b->next;
- }
- tail = tail->next;
- }
- tail->next = a ? a : b;
-
- do {
- /*
- * In worst cases this loop may run many iterations.
- * Continue callbacks to the client even though no
- * element comparison is needed, so the client's cmp()
- * routine can invoke cond_resched() periodically.
- */
- (*cmp)(priv, tail->next, tail->next);
-
- tail->next->prev = tail;
- tail = tail->next;
- } while (tail->next);
-
- tail->next = head;
- head->prev = tail;
-}
-
-/**
- * list_sort - sort a list
- * @priv: private data, opaque to list_sort(), passed to @cmp
- * @head: the list to sort
- * @cmp: the elements comparison function
- *
- * This function implements "merge sort", which has O(nlog(n))
- * complexity.
- *
- * The comparison function @cmp must return a negative value if @a
- * should sort before @b, and a positive value if @a should sort after
- * @b. If @a and @b are equivalent, and their original relative
- * ordering is to be preserved, @cmp must return 0.
- */
-void list_sort(void *priv, struct list_head *head,
- int (*cmp)(void *priv, struct list_head *a,
- struct list_head *b))
-{
- struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
- -- last slot is a sentinel */
- int lev; /* index into part[] */
- int max_lev = 0;
- struct list_head *list;
-
- if (list_empty(head))
- return;
-
- memset(part, 0, sizeof(part));
-
- head->prev->next = NULL;
- list = head->next;
-
- while (list) {
- struct list_head *cur = list;
- list = list->next;
- cur->next = NULL;
-
- for (lev = 0; part[lev]; lev++) {
- cur = merge(priv, cmp, part[lev], cur);
- part[lev] = NULL;
- }
- if (lev > max_lev) {
- max_lev = lev;
- }
- part[lev] = cur;
- }
-
- for (lev = 0; lev < max_lev; lev++)
- if (part[lev])
- list = merge(priv, cmp, part[lev], list);
-
- merge_and_restore_back_links(priv, cmp, head, part[max_lev], list);
-}
diff --git a/src/util/list_sort.h b/src/util/list_sort.h
deleted file mode 100644
index 7a65020b9..000000000
--- a/src/util/list_sort.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.
- */
-
-/*
- * This code was imported from the Linux kernel.
- *
- */
-
-#ifndef _LINUX_LIST_SORT_H
-#define _LINUX_LIST_SORT_H
-
-struct list_head;
-
-void list_sort(void *priv, struct list_head *head,
- int (*cmp)(void *priv, struct list_head *a,
- struct list_head *b));
-#endif
diff --git a/src/win32/Win32Main.cxx b/src/win32/Win32Main.cxx
index 0d2a70348..75a1e9a23 100644
--- a/src/win32/Win32Main.cxx
+++ b/src/win32/Win32Main.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/zeroconf/AvahiPoll.cxx b/src/zeroconf/AvahiPoll.cxx
new file mode 100644
index 000000000..20d5d74e6
--- /dev/null
+++ b/src/zeroconf/AvahiPoll.cxx
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AvahiPoll.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/TimeoutMonitor.hxx"
+
+static unsigned
+FromAvahiWatchEvent(AvahiWatchEvent e)
+{
+ return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) |
+ (e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) |
+ (e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) |
+ (e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0);
+}
+
+static AvahiWatchEvent
+ToAvahiWatchEvent(unsigned e)
+{
+ return AvahiWatchEvent((e & SocketMonitor::READ ? AVAHI_WATCH_IN : 0) |
+ (e & SocketMonitor::WRITE ? AVAHI_WATCH_OUT : 0) |
+ (e & SocketMonitor::ERROR ? AVAHI_WATCH_ERR : 0) |
+ (e & SocketMonitor::HANGUP ? AVAHI_WATCH_HUP : 0));
+}
+
+struct AvahiWatch final : private SocketMonitor {
+private:
+ const AvahiWatchCallback callback;
+ void *const userdata;
+
+ AvahiWatchEvent received;
+
+public:
+ AvahiWatch(int _fd, AvahiWatchEvent _event,
+ AvahiWatchCallback _callback, void *_userdata,
+ EventLoop &_loop)
+ :SocketMonitor(_fd, _loop),
+ callback(_callback), userdata(_userdata),
+ received(AvahiWatchEvent(0)) {
+ Schedule(FromAvahiWatchEvent(_event));
+ }
+
+ static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) {
+ w->Schedule(FromAvahiWatchEvent(event));
+ }
+
+ static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) {
+ return w->received;
+ }
+
+ static void WatchFree(AvahiWatch *w) {
+ delete w;
+ }
+
+protected:
+ virtual bool OnSocketReady(unsigned flags) {
+ received = ToAvahiWatchEvent(flags);
+ callback(this, Get(), received, userdata);
+ received = AvahiWatchEvent(0);
+ return true;
+ }
+};
+
+static constexpr unsigned
+TimevalToMS(const timeval &tv)
+{
+ return tv.tv_sec * 1000 + (tv.tv_usec + 500) / 1000;
+}
+
+struct AvahiTimeout final : private TimeoutMonitor {
+private:
+ const AvahiTimeoutCallback callback;
+ void *const userdata;
+
+public:
+ AvahiTimeout(const struct timeval *tv,
+ AvahiTimeoutCallback _callback, void *_userdata,
+ EventLoop &_loop)
+ :TimeoutMonitor(_loop),
+ callback(_callback), userdata(_userdata) {
+ if (tv != nullptr)
+ Schedule(TimevalToMS(*tv));
+ }
+
+ static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) {
+ if (tv != nullptr)
+ t->Schedule(TimevalToMS(*tv));
+ else
+ t->Cancel();
+ }
+
+ static void TimeoutFree(AvahiTimeout *t) {
+ delete t;
+ }
+
+protected:
+ virtual void OnTimeout() {
+ callback(this, userdata);
+ }
+};
+
+MyAvahiPoll::MyAvahiPoll(EventLoop &_loop):event_loop(_loop)
+{
+ watch_new = WatchNew;
+ watch_update = AvahiWatch::WatchUpdate;
+ watch_get_events = AvahiWatch::WatchGetEvents;
+ watch_free = AvahiWatch::WatchFree;
+ timeout_new = TimeoutNew;
+ timeout_update = AvahiTimeout::TimeoutUpdate;
+ timeout_free = AvahiTimeout::TimeoutFree;
+}
+
+AvahiWatch *
+MyAvahiPoll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event,
+ AvahiWatchCallback callback, void *userdata) {
+ const MyAvahiPoll &poll = *(const MyAvahiPoll *)api;
+
+ return new AvahiWatch(fd, event, callback, userdata,
+ poll.event_loop);
+}
+
+AvahiTimeout *
+MyAvahiPoll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv,
+ AvahiTimeoutCallback callback, void *userdata) {
+ const MyAvahiPoll &poll = *(const MyAvahiPoll *)api;
+
+ return new AvahiTimeout(tv, callback, userdata,
+ poll.event_loop);
+}
diff --git a/src/zeroconf/AvahiPoll.hxx b/src/zeroconf/AvahiPoll.hxx
new file mode 100644
index 000000000..e194d3370
--- /dev/null
+++ b/src/zeroconf/AvahiPoll.hxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AVAHI_POLL_HXX
+#define MPD_AVAHI_POLL_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <avahi-common/watch.h>
+
+class EventLoop;
+
+class MyAvahiPoll final : public AvahiPoll {
+ EventLoop &event_loop;
+
+public:
+ MyAvahiPoll(EventLoop &_loop);
+
+private:
+ static AvahiWatch *WatchNew(const AvahiPoll *api, int fd,
+ AvahiWatchEvent event,
+ AvahiWatchCallback callback,
+ void *userdata);
+
+ static AvahiTimeout *TimeoutNew(const AvahiPoll *api,
+ const struct timeval *tv,
+ AvahiTimeoutCallback callback,
+ void *userdata);
+};
+
+#endif
diff --git a/src/zeroconf/ZeroconfAvahi.cxx b/src/zeroconf/ZeroconfAvahi.cxx
new file mode 100644
index 000000000..5adda38f9
--- /dev/null
+++ b/src/zeroconf/ZeroconfAvahi.cxx
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ZeroconfAvahi.hxx"
+#include "AvahiPoll.hxx"
+#include "ZeroconfInternal.hxx"
+#include "Listen.hxx"
+#include "system/FatalError.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#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 <dbus/dbus.h>
+
+static constexpr Domain avahi_domain("avahi");
+
+static char *avahi_name;
+static MyAvahiPoll *avahi_poll;
+static AvahiClient *avahi_client;
+static AvahiEntryGroup *avahi_group;
+
+static void
+AvahiRegisterService(AvahiClient *c);
+
+/**
+ * Callback when the EntryGroup changes state.
+ */
+static void
+AvahiGroupCallback(AvahiEntryGroup *g,
+ AvahiEntryGroupState state,
+ gcc_unused void *userdata)
+{
+ assert(g != nullptr);
+
+ FormatDebug(avahi_domain,
+ "Service group changed to state %d", state);
+
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ /* The entry group has been established successfully */
+ FormatDefault(avahi_domain,
+ "Service '%s' successfully established.",
+ avahi_name);
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ /* A service name collision happened. Let's pick a new name */
+ {
+ char *n = avahi_alternative_service_name(avahi_name);
+ avahi_free(avahi_name);
+ avahi_name = n;
+ }
+
+ FormatDefault(avahi_domain,
+ "Service name collision, renaming service to '%s'",
+ avahi_name);
+
+ /* And recreate the services */
+ AvahiRegisterService(avahi_entry_group_get_client(g));
+ break;
+
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ FormatError(avahi_domain,
+ "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 */
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ LogDebug(avahi_domain, "Service group is UNCOMMITED");
+ break;
+
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ LogDebug(avahi_domain, "Service group is REGISTERING");
+ }
+}
+
+/**
+ * Registers a new service with avahi.
+ */
+static void
+AvahiRegisterService(AvahiClient *c)
+{
+ assert(c != nullptr);
+
+ FormatDebug(avahi_domain, "Registering service %s/%s",
+ SERVICE_TYPE, avahi_name);
+
+ /* If this is the first time we're called,
+ * let's create a new entry group */
+ if (!avahi_group) {
+ avahi_group = avahi_entry_group_new(c, AvahiGroupCallback, nullptr);
+ if (!avahi_group) {
+ FormatError(avahi_domain,
+ "Failed to create avahi EntryGroup: %s",
+ avahi_strerror(avahi_client_errno(c)));
+ return;
+ }
+ }
+
+ /* Add the service */
+ /* TODO: This currently binds to ALL interfaces.
+ * We could maybe add a service per actual bound interface,
+ * if that's better. */
+ int result = avahi_entry_group_add_service(avahi_group,
+ AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ AvahiPublishFlags(0),
+ avahi_name, SERVICE_TYPE,
+ nullptr, nullptr,
+ listen_port, nullptr);
+ if (result < 0) {
+ FormatError(avahi_domain, "Failed to add service %s: %s",
+ SERVICE_TYPE, avahi_strerror(result));
+ return;
+ }
+
+ /* Tell the server to register the service group */
+ result = avahi_entry_group_commit(avahi_group);
+ if (result < 0) {
+ FormatError(avahi_domain, "Failed to commit service group: %s",
+ avahi_strerror(result));
+ return;
+ }
+}
+
+/* Callback when avahi changes state */
+static void
+MyAvahiClientCallback(AvahiClient *c, AvahiClientState state,
+ gcc_unused void *userdata)
+{
+ assert(c != nullptr);
+
+ /* Called whenever the client or server state changes */
+ FormatDebug(avahi_domain, "Client changed to state %d", state);
+
+ switch (state) {
+ int reason;
+
+ case AVAHI_CLIENT_S_RUNNING:
+ LogDebug(avahi_domain, "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 (avahi_group == nullptr)
+ AvahiRegisterService(c);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ reason = avahi_client_errno(c);
+ if (reason == AVAHI_ERR_DISCONNECTED) {
+ LogDefault(avahi_domain,
+ "Client Disconnected, will reconnect shortly");
+ if (avahi_group != nullptr) {
+ avahi_entry_group_free(avahi_group);
+ avahi_group = nullptr;
+ }
+
+ if (avahi_client != nullptr)
+ avahi_client_free(avahi_client);
+ avahi_client =
+ avahi_client_new(avahi_poll,
+ AVAHI_CLIENT_NO_FAIL,
+ MyAvahiClientCallback, nullptr,
+ &reason);
+ if (avahi_client == nullptr)
+ FormatWarning(avahi_domain,
+ "Could not reconnect: %s",
+ avahi_strerror(reason));
+ } else {
+ FormatWarning(avahi_domain,
+ "Client failure: %s (terminal)",
+ avahi_strerror(reason));
+ }
+
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ LogDebug(avahi_domain, "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 (avahi_group != nullptr) {
+ LogDebug(avahi_domain, "Resetting group");
+ avahi_entry_group_reset(avahi_group);
+ }
+
+ break;
+
+ case AVAHI_CLIENT_S_REGISTERING:
+ LogDebug(avahi_domain, "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 (avahi_group != nullptr) {
+ LogDebug(avahi_domain, "Resetting group");
+ avahi_entry_group_reset(avahi_group);
+ }
+
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ LogDebug(avahi_domain, "Client is CONNECTING");
+ break;
+ }
+}
+
+void
+AvahiInit(EventLoop &loop, const char *serviceName)
+{
+ LogDebug(avahi_domain, "Initializing interface");
+
+ if (!avahi_is_valid_service_name(serviceName))
+ FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName);
+
+ avahi_name = avahi_strdup(serviceName);
+
+ avahi_poll = new MyAvahiPoll(loop);
+
+ int error;
+ avahi_client = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
+ MyAvahiClientCallback, nullptr,
+ &error);
+ if (avahi_client == nullptr) {
+ FormatError(avahi_domain, "Failed to create client: %s",
+ avahi_strerror(error));
+ AvahiDeinit();
+ }
+}
+
+void
+AvahiDeinit()
+{
+ LogDebug(avahi_domain, "Shutting down interface");
+
+ if (avahi_group != nullptr) {
+ avahi_entry_group_free(avahi_group);
+ avahi_group = nullptr;
+ }
+
+ if (avahi_client != nullptr) {
+ avahi_client_free(avahi_client);
+ avahi_client = nullptr;
+ }
+
+ delete avahi_poll;
+ avahi_poll = nullptr;
+
+ avahi_free(avahi_name);
+ avahi_name = nullptr;
+
+ dbus_shutdown();
+}
diff --git a/src/zeroconf/ZeroconfAvahi.hxx b/src/zeroconf/ZeroconfAvahi.hxx
new file mode 100644
index 000000000..09a199f55
--- /dev/null
+++ b/src/zeroconf/ZeroconfAvahi.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/zeroconf/ZeroconfBonjour.cxx b/src/zeroconf/ZeroconfBonjour.cxx
new file mode 100644
index 000000000..8d7565e0e
--- /dev/null
+++ b/src/zeroconf/ZeroconfBonjour.cxx
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ZeroconfBonjour.hxx"
+#include "ZeroconfInternal.hxx"
+#include "Listen.hxx"
+#include "event/SocketMonitor.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+#include "Compiler.h"
+
+#include <dns_sd.h>
+
+#include <arpa/inet.h>
+
+static constexpr Domain bonjour_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() {
+ DNSServiceRefDeallocate(service_ref);
+ }
+
+protected:
+ virtual bool OnSocketReady(gcc_unused unsigned flags) override {
+ DNSServiceProcessResult(service_ref);
+ return false;
+ }
+};
+
+static BonjourMonitor *bonjour_monitor;
+
+static void
+dnsRegisterCallback(gcc_unused DNSServiceRef sdRef,
+ gcc_unused DNSServiceFlags flags,
+ DNSServiceErrorType errorCode, const char *name,
+ gcc_unused const char *regtype,
+ gcc_unused const char *domain,
+ gcc_unused void *context)
+{
+ if (errorCode != kDNSServiceErr_NoError) {
+ LogError(bonjour_domain,
+ "Failed to register zeroconf service");
+
+ bonjour_monitor->Cancel();
+ } else {
+ FormatDebug(bonjour_domain,
+ "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, nullptr, nullptr,
+ htons(listen_port), 0,
+ nullptr,
+ dnsRegisterCallback,
+ nullptr);
+
+ if (error != kDNSServiceErr_NoError) {
+ LogError(bonjour_domain,
+ "Failed to register zeroconf service");
+
+ if (dnsReference) {
+ DNSServiceRefDeallocate(dnsReference);
+ dnsReference = nullptr;
+ }
+ return;
+ }
+
+ bonjour_monitor = new BonjourMonitor(loop, dnsReference);
+}
+
+void
+BonjourDeinit()
+{
+ delete bonjour_monitor;
+}
diff --git a/src/zeroconf/ZeroconfBonjour.hxx b/src/zeroconf/ZeroconfBonjour.hxx
new file mode 100644
index 000000000..cff52815e
--- /dev/null
+++ b/src/zeroconf/ZeroconfBonjour.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/zeroconf/ZeroconfGlue.cxx b/src/zeroconf/ZeroconfGlue.cxx
new file mode 100644
index 000000000..95797491b
--- /dev/null
+++ b/src/zeroconf/ZeroconfGlue.cxx
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ZeroconfGlue.hxx"
+#include "ZeroconfAvahi.hxx"
+#include "ZeroconfBonjour.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
+#include "Listen.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+#include "Compiler.h"
+
+static constexpr Domain zeroconf_domain("zeroconf");
+
+/* 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) {
+ LogWarning(zeroconf_domain,
+ "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/zeroconf/ZeroconfGlue.hxx b/src/zeroconf/ZeroconfGlue.hxx
new file mode 100644
index 000000000..5d2f29642
--- /dev/null
+++ b/src/zeroconf/ZeroconfGlue.hxx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_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/zeroconf/ZeroconfInternal.hxx b/src/zeroconf/ZeroconfInternal.hxx
new file mode 100644
index 000000000..4d47d260a
--- /dev/null
+++ b/src/zeroconf/ZeroconfInternal.hxx
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef ZEROCONF_INTERNAL_H
+#define ZEROCONF_INTERNAL_H
+
+/* The dns-sd service type qualifier to publish */
+#define SERVICE_TYPE "_mpd._tcp"
+
+#endif
diff --git a/systemd/mpd.service.in b/systemd/mpd.service.in
new file mode 100644
index 000000000..a72eb925c
--- /dev/null
+++ b/systemd/mpd.service.in
@@ -0,0 +1,22 @@
+[Unit]
+Description=Music Player Daemon
+After=network.target sound.target
+
+[Service]
+ExecStart=@prefix@/bin/mpd --no-daemon
+
+# allow MPD to use real-time priority 50
+LimitRTPRIO=50
+LimitRTTIME=-1
+
+# move MPD to a top-level cgroup, as real-time budget assignment fails
+# in cgroup /system/mpd.service, because /system has a zero real-time
+# budget; see
+# http://www.freedesktop.org/wiki/Software/systemd/MyServiceCantGetRealtime/
+ControlGroup=cpu:/mpd
+
+# assign a real-time budget
+ControlGroupAttribute=cpu.rt_runtime_us 500000
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd/mpd.socket b/systemd/mpd.socket
new file mode 100644
index 000000000..c4692592c
--- /dev/null
+++ b/systemd/mpd.socket
@@ -0,0 +1,9 @@
+[Socket]
+ListenStream=/run/mpd/socket
+ListenStream=6600
+Backlog=5
+KeepAlive=true
+PassCredentials=true
+
+[Install]
+WantedBy=sockets.target
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 000000000..132ce5fcf
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+/run_neighbor_explorer
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index 21a12d294..07f342319 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,19 +18,24 @@
*/
#include "config.h"
-#include "DatabaseRegistry.hxx"
-#include "DatabasePlugin.hxx"
-#include "DatabaseSelection.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
-#include "PlaylistVector.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigData.hxx"
+#include "db/Registry.hxx"
+#include "db/DatabasePlugin.hxx"
+#include "db/Interface.hxx"
+#include "db/Selection.hxx"
+#include "db/DatabaseListener.hxx"
+#include "db/LightDirectory.hxx"
+#include "db/LightSong.hxx"
+#include "db/PlaylistVector.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
#include "tag/TagConfig.hxx"
#include "fs/Path.hxx"
+#include "event/Loop.hxx"
#include "util/Error.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <iostream>
using std::cout;
@@ -39,35 +44,49 @@ using std::endl;
#include <stdlib.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
+#ifdef HAVE_LIBUPNP
+#include "input/InputStream.hxx"
+size_t
+InputStream::LockRead(void *, size_t, Error &)
{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
+ return 0;
}
+#endif
+
+class MyDatabaseListener final : public DatabaseListener {
+public:
+ virtual void OnDatabaseModified() override {
+ cout << "DatabaseModified" << endl;
+ }
+
+ virtual void OnDatabaseSongRemoved(const LightSong &song) override {
+ cout << "SongRemoved " << song.GetURI() << endl;
+ }
+};
static bool
-DumpDirectory(const Directory &directory, Error &)
+DumpDirectory(const LightDirectory &directory, Error &)
{
- cout << "D " << directory.path << endl;
+ cout << "D " << directory.GetPath() << endl;
return true;
}
static bool
-DumpSong(Song &song, Error &)
+DumpSong(const LightSong &song, Error &)
{
- cout << "S " << song.parent->path << "/" << song.uri << endl;
+ cout << "S ";
+ if (song.directory != nullptr)
+ cout << song.directory << "/";
+ cout << song.uri << endl;
return true;
}
static bool
DumpPlaylist(const PlaylistInfo &playlist,
- const Directory &directory, Error &)
+ const LightDirectory &directory, Error &)
{
- cout << "P " << directory.path << "/" << playlist.name.c_str() << endl;
+ cout << "P " << directory.GetPath()
+ << "/" << playlist.name.c_str() << endl;
return true;
}
@@ -90,11 +109,11 @@ main(int argc, char **argv)
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(nullptr);
#endif
-
- g_log_set_default_handler(my_log_func, nullptr);
+#endif
/* initialize MPD */
@@ -108,14 +127,18 @@ main(int argc, char **argv)
TagLoadConfig();
+ EventLoop event_loop;
+ MyDatabaseListener database_listener;
+
/* do it */
const struct config_param *path = config_get_param(CONF_DB_FILE);
- config_param param("database", path->line);
+ config_param param("database", path != nullptr ? path->line : -1);
if (path != nullptr)
param.AddBlockParam("path", path->value.c_str(), path->line);
- Database *db = plugin->create(param, error);
+ Database *db = plugin->create(event_loop, database_listener,
+ param, error);
if (db == nullptr) {
cerr << error.GetMessage() << endl;
diff --git a/test/FakeDecoderAPI.cxx b/test/FakeDecoderAPI.cxx
index ca09ca982..3c7453a4f 100644
--- a/test/FakeDecoderAPI.cxx
+++ b/test/FakeDecoderAPI.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,21 +18,30 @@
*/
#include "config.h"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "FakeDecoderAPI.hxx"
+#include "decoder/DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "util/Error.hxx"
#include "Compiler.h"
-#include <glib.h>
-
#include <unistd.h>
void
-decoder_initialized(gcc_unused Decoder &decoder,
- gcc_unused const AudioFormat audio_format,
+decoder_initialized(Decoder &decoder,
+ const AudioFormat audio_format,
gcc_unused bool seekable,
- gcc_unused float total_time)
+ SignedSongTime duration)
{
+ struct audio_format_string af_string;
+
+ assert(!decoder.initialized);
+ assert(audio_format.IsValid());
+
+ fprintf(stderr, "audio_format=%s duration=%f\n",
+ audio_format_to_string(audio_format, &af_string),
+ duration.ToDoubleS());
+
+ decoder.initialized = true;
}
DecoderCommand
@@ -46,10 +55,16 @@ decoder_command_finished(gcc_unused Decoder &decoder)
{
}
-double
-decoder_seek_where(gcc_unused Decoder &decoder)
+SongTime
+decoder_seek_time(gcc_unused Decoder &decoder)
+{
+ return SongTime();
+}
+
+uint64_t
+decoder_seek_where_frame(gcc_unused Decoder &decoder)
{
- return 1.0;
+ return 1;
}
void
@@ -57,6 +72,12 @@ decoder_seek_error(gcc_unused Decoder &decoder)
{
}
+InputStream *
+decoder_open_uri(Decoder &decoder, const char *uri, Error &error)
+{
+ return InputStream::OpenReady(uri, decoder.mutex, decoder.cond, error);
+}
+
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
@@ -111,6 +132,12 @@ decoder_data(gcc_unused Decoder &decoder,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
+ static uint16_t prev_kbit_rate;
+ if (kbit_rate != prev_kbit_rate) {
+ prev_kbit_rate = kbit_rate;
+ fprintf(stderr, "%u kbit/s\n", kbit_rate);
+ }
+
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
@@ -129,16 +156,18 @@ decoder_replay_gain(gcc_unused Decoder &decoder,
{
const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
+ fprintf(stderr, "MixRamp: start='%s' end='%s'\n",
+ mix_ramp.GetStart(), mix_ramp.GetEnd());
}
diff --git a/test/FakeDecoderAPI.hxx b/test/FakeDecoderAPI.hxx
new file mode 100644
index 000000000..6f1933977
--- /dev/null
+++ b/test/FakeDecoderAPI.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FAKE_DECODER_API_HXX
+#define FAKE_DECODER_API_HXX
+
+#include "check.h"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+struct Decoder {
+ Mutex mutex;
+ Cond cond;
+
+ bool initialized;
+
+ Decoder()
+ :initialized(false) {}
+};
+
+#endif
diff --git a/test/FakeReplayGainConfig.cxx b/test/FakeReplayGainConfig.cxx
index 3305b79a3..0cb282050 100644
--- a/test/FakeReplayGainConfig.cxx
+++ b/test/FakeReplayGainConfig.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/FakeSong.cxx b/test/FakeSong.cxx
deleted file mode 100644
index c0859d7b1..000000000
--- a/test/FakeSong.cxx
+++ /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.
- */
-
-#include "config.h"
-#include "Song.hxx"
-#include "directory.h"
-#include "Compiler.h"
-
-#include <stdlib.h>
-
-struct directory detached_root;
-
-Song *
-song_dup_detached(gcc_unused const Song *src)
-{
- abort();
-}
diff --git a/test/ScopeIOThread.hxx b/test/ScopeIOThread.hxx
new file mode 100644
index 000000000..06d27a4b8
--- /dev/null
+++ b/test/ScopeIOThread.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SCOPE_IO_THREAD_HXX
+#define MPD_SCOPE_IO_THREAD_HXX
+
+#include "IOThread.hxx"
+
+struct ScopeIOThread {
+ ScopeIOThread() {
+ io_thread_init();
+ io_thread_start();
+ }
+
+ ~ScopeIOThread() {
+ io_thread_deinit();
+ }
+};
+
+#endif
diff --git a/test/ShutdownHandler.cxx b/test/ShutdownHandler.cxx
index 341a92939..c04834444 100644
--- a/test/ShutdownHandler.cxx
+++ b/test/ShutdownHandler.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/ShutdownHandler.hxx b/test/ShutdownHandler.hxx
index 0a84ed63f..9db88a1b4 100644
--- a/test/ShutdownHandler.hxx
+++ b/test/ShutdownHandler.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,7 +24,7 @@ class EventLoop;
class ShutdownHandler {
public:
- ShutdownHandler(EventLoop &loop);
+ explicit ShutdownHandler(EventLoop &loop);
~ShutdownHandler();
};
diff --git a/test/TestCircularBuffer.hxx b/test/TestCircularBuffer.hxx
new file mode 100644
index 000000000..c808d85dc
--- /dev/null
+++ b/test/TestCircularBuffer.hxx
@@ -0,0 +1,163 @@
+/*
+ * Unit tests for class CircularBuffer.
+ */
+
+#include "config.h"
+#include "util/CircularBuffer.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+class TestCircularBuffer : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TestCircularBuffer);
+ CPPUNIT_TEST(TestIt);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestIt() {
+ static size_t N = 8;
+ int data[N];
+ CircularBuffer<int> buffer(data, N);
+
+ CPPUNIT_ASSERT_EQUAL(size_t(N), buffer.GetCapacity());
+
+ /* '.' = empty; 'O' = occupied; 'X' = blocked */
+
+ /* checks on empty buffer */
+ /* [.......X] */
+ CPPUNIT_ASSERT_EQUAL(true, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(true, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[0], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.Write().size);
+
+ /* append one element */
+ /* [O......X] */
+ buffer.Append(1);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(6), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[0], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[1], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(6), buffer.Write().size);
+
+ /* append 6 elements, buffer is now full */
+ /* [OOOOOOOX] */
+ buffer.Append(6);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(true, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[0], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(true, buffer.Write().IsEmpty());
+
+ /* consume [0]; can append one at [7] */
+ /* [XOOOOOO.] */
+ buffer.Consume(1);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(6), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(6), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[1], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[7], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), buffer.Write().size);
+
+ /* append one element; [0] is still empty but cannot
+ be written to because head==1 */
+ /* [XOOOOOOO] */
+ buffer.Append(1);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(true, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[1], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(true, buffer.Write().IsEmpty());
+
+ /* consume [1..3]; can append [0..2] */
+ /* [...XOOOO] */
+ buffer.Consume(3);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(3), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[4], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[0], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(3), buffer.Write().size);
+
+ /* append [0..1] */
+ /* [OO.XOOOO] */
+ buffer.Append(2);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(6), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(1), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[4], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[2], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), buffer.Write().size);
+
+ /* append [2] */
+ /* [OOOXOOOO] */
+ buffer.Append(1);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(true, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[4], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(true, buffer.Write().IsEmpty());
+
+ /* consume [4..7] */
+ /* [OOO....X] */
+ buffer.Consume(4);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(size_t(3), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(4), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(size_t(3), buffer.Read().size);
+ CPPUNIT_ASSERT_EQUAL(&data[0], buffer.Read().data);
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[3], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(4), buffer.Write().size);
+
+ /* consume [0..2]; after that, we can only write 5,
+ because the CircularBuffer class doesn't have
+ special code to rewind/reset an empty buffer */
+ /* [..X.....] */
+ buffer.Consume(3);
+ CPPUNIT_ASSERT_EQUAL(true, buffer.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.IsFull());
+ CPPUNIT_ASSERT_EQUAL(size_t(0), buffer.GetSize());
+ CPPUNIT_ASSERT_EQUAL(size_t(7), buffer.GetSpace());
+ CPPUNIT_ASSERT_EQUAL(true, buffer.Read().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(false, buffer.Write().IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(&data[3], buffer.Write().data);
+ CPPUNIT_ASSERT_EQUAL(size_t(5), buffer.Write().size);
+ }
+};
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index 4ac02985a..0047ef427 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,48 +19,46 @@
#include "config.h"
#include "TagSave.hxx"
-#include "Song.hxx"
-#include "SongEnumerator.hxx"
-#include "Directory.hxx"
-#include "InputStream.hxx"
-#include "ConfigGlobal.hxx"
-#include "DecoderList.hxx"
-#include "InputInit.hxx"
-#include "IOThread.hxx"
-#include "PlaylistRegistry.hxx"
-#include "PlaylistPlugin.hxx"
+#include "DetachedSong.hxx"
+#include "playlist/SongEnumerator.hxx"
+#include "input/InputStream.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "decoder/DecoderList.hxx"
+#include "input/Init.hxx"
+#include "ScopeIOThread.hxx"
+#include "playlist/PlaylistRegistry.hxx"
+#include "playlist/PlaylistPlugin.hxx"
#include "fs/Path.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "fs/io/StdioOutputStream.hxx"
#include "util/Error.hxx"
#include "thread/Cond.hxx"
#include "Log.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdlib.h>
-Directory::Directory() {}
-Directory::~Directory() {}
-
static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
+tag_save(FILE *file, const Tag &tag)
{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
+ StdioOutputStream sos(file);
+ BufferedOutputStream bos(sos);
+ tag_save(bos, tag);
+ bos.Flush();
}
int main(int argc, char **argv)
{
const char *uri;
InputStream *is = NULL;
- Song *song;
if (argc != 3) {
- g_printerr("Usage: dump_playlist CONFIG URI\n");
- return 1;
+ fprintf(stderr, "Usage: dump_playlist CONFIG URI\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
@@ -68,11 +66,11 @@ int main(int argc, char **argv)
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
/* initialize MPD */
@@ -80,16 +78,15 @@ int main(int argc, char **argv)
Error error;
if (!ReadConfigFile(config_path, error)) {
- g_printerr("%s\n", error.GetMessage());
- return 1;
+ LogError(error);
+ return EXIT_FAILURE;
}
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
if (!input_stream_global_init(error)) {
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
playlist_list_global_init();
@@ -104,59 +101,59 @@ int main(int argc, char **argv)
if (playlist == NULL) {
/* open the stream and wait until it becomes ready */
- is = InputStream::Open(uri, mutex, cond, error);
+ is = InputStream::OpenReady(uri, mutex, cond, error);
if (is == NULL) {
if (error.IsDefined())
LogError(error);
else
- g_printerr("InputStream::Open() failed\n");
+ fprintf(stderr,
+ "InputStream::Open() failed\n");
return 2;
}
- is->LockWaitReady();
-
/* open the playlist */
playlist = playlist_list_open_stream(*is, uri);
if (playlist == NULL) {
- is->Close();
- g_printerr("Failed to open playlist\n");
+ delete is;
+ fprintf(stderr, "Failed to open playlist\n");
return 2;
}
}
/* dump the playlist */
+ DetachedSong *song;
while ((song = playlist->NextSong()) != NULL) {
- g_print("%s\n", song->uri);
-
- if (song->end_ms > 0)
- g_print("range: %u:%02u..%u:%02u\n",
- song->start_ms / 60000,
- (song->start_ms / 1000) % 60,
- song->end_ms / 60000,
- (song->end_ms / 1000) % 60);
- else if (song->start_ms > 0)
- g_print("range: %u:%02u..\n",
- song->start_ms / 60000,
- (song->start_ms / 1000) % 60);
-
- if (song->tag != NULL)
- tag_save(stdout, *song->tag);
-
- song->Free();
+ printf("%s\n", song->GetURI());
+
+ const unsigned start_ms = song->GetStartTime().ToMS();
+ const unsigned end_ms = song->GetEndTime().ToMS();
+
+ if (end_ms > 0)
+ printf("range: %u:%02u..%u:%02u\n",
+ start_ms / 60000,
+ (start_ms / 1000) % 60,
+ end_ms / 60000,
+ (end_ms / 1000) % 60);
+ else if (start_ms > 0)
+ printf("range: %u:%02u..\n",
+ start_ms / 60000,
+ (start_ms / 1000) % 60);
+
+ tag_save(stdout, song->GetTag());
+
+ delete song;
}
/* deinitialize everything */
delete playlist;
- if (is != NULL)
- is->Close();
+ delete 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.cxx b/test/dump_rva2.cxx
index e1ba5336a..fd46ee36c 100644
--- a/test/dump_rva2.cxx
+++ b/test/dump_rva2.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,19 +21,19 @@
#include "tag/TagId3.hxx"
#include "tag/TagRva2.hxx"
#include "ReplayGainInfo.hxx"
-#include "ConfigGlobal.hxx"
+#include "config/ConfigGlobal.hxx"
#include "util/Error.hxx"
#include "fs/Path.hxx"
+#include "Log.hxx"
#include <id3tag.h>
-#include <glib.h>
-
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include <stdlib.h>
+#include <stdio.h>
const char *
config_get_string(gcc_unused enum ConfigOption option,
@@ -50,8 +50,8 @@ int main(int argc, char **argv)
#endif
if (argc != 2) {
- g_printerr("Usage: read_rva2 FILE\n");
- return 1;
+ fprintf(stderr, "Usage: read_rva2 FILE\n");
+ return EXIT_FAILURE;
}
const char *path = argv[1];
@@ -60,9 +60,9 @@ int main(int argc, char **argv)
struct id3_tag *tag = tag_id3_load(Path::FromFS(path), error);
if (tag == NULL) {
if (error.IsDefined())
- g_printerr("%s\n", error.GetMessage());
+ LogError(error);
else
- g_printerr("No ID3 tag found\n");
+ fprintf(stderr, "No ID3 tag found\n");
return EXIT_FAILURE;
}
@@ -74,19 +74,19 @@ int main(int argc, char **argv)
id3_tag_delete(tag);
if (!success) {
- g_printerr("No RVA2 tag found\n");
+ fprintf(stderr, "No RVA2 tag found\n");
return EXIT_FAILURE;
}
const ReplayGainTuple *tuple = &replay_gain.tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
+ fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n",
+ tuple->gain, tuple->peak);
return EXIT_SUCCESS;
}
diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx
index bb84f5cce..5bfd316a5 100644
--- a/test/dump_text_file.cxx
+++ b/test/dump_text_file.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,64 +18,39 @@
*/
#include "config.h"
-#include "IOThread.hxx"
-#include "InputInit.hxx"
-#include "InputStream.hxx"
-#include "ConfigGlobal.hxx"
+#include "ScopeIOThread.hxx"
+#include "input/Init.hxx"
+#include "input/InputStream.hxx"
+#include "input/TextInputStream.hxx"
+#include "config/ConfigGlobal.hxx"
#include "stdbin.h"
-#include "TextInputStream.hxx"
#include "util/Error.hxx"
#include "thread/Cond.hxx"
#include "Log.hxx"
#ifdef ENABLE_ARCHIVE
-#include "ArchiveList.hxx"
+#include "archive/ArchiveList.hxx"
#endif
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
-static void
dump_text_file(TextInputStream &is)
{
- std::string line;
- while (is.ReadLine(line))
- printf("'%s'\n", line.c_str());
+ const char *line;
+ while ((line = is.ReadLine()) != nullptr)
+ printf("'%s'\n", line);
}
static int
dump_input_stream(InputStream &is)
{
- Error error;
-
- is.Lock();
-
- /* wait until the stream becomes ready */
-
- is.WaitReady();
-
- if (!is.Check(error)) {
- LogError(error);
- is.Unlock();
- return EXIT_FAILURE;
- }
-
- /* read data and tags from the stream */
-
- is.Unlock();
{
TextInputStream tis(is);
dump_text_file(tis);
@@ -83,6 +58,7 @@ dump_input_stream(InputStream &is)
is.Lock();
+ Error error;
if (!is.Check(error)) {
LogError(error);
is.Unlock();
@@ -99,24 +75,23 @@ int main(int argc, char **argv)
int ret;
if (argc != 2) {
- g_printerr("Usage: run_input URI\n");
- return 1;
+ fprintf(stderr, "Usage: run_input URI\n");
+ return EXIT_FAILURE;
}
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
/* initialize MPD */
config_global_init();
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
@@ -133,16 +108,16 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
- InputStream *is = InputStream::Open(argv[1], mutex, cond, error);
+ InputStream *is = InputStream::OpenReady(argv[1], mutex, cond, error);
if (is != NULL) {
ret = dump_input_stream(*is);
- is->Close();
+ delete is;
} else {
if (error.IsDefined())
LogError(error);
else
- g_printerr("input_stream::Open() failed\n");
- ret = 2;
+ fprintf(stderr, "input_stream::Open() failed\n");
+ ret = EXIT_FAILURE;
}
/* deinitialize everything */
@@ -153,8 +128,6 @@ int main(int argc, char **argv)
archive_plugin_deinit_all();
#endif
- io_thread_deinit();
-
config_global_finish();
return ret;
diff --git a/test/read_conf.cxx b/test/read_conf.cxx
index d5eacec67..42afdfb4b 100644
--- a/test/read_conf.cxx
+++ b/test/read_conf.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,43 +18,31 @@
*/
#include "config.h"
-#include "ConfigGlobal.hxx"
+#include "config/ConfigGlobal.hxx"
#include "fs/Path.hxx"
#include "util/Error.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
#include <assert.h>
-
-static void
-my_log_func(gcc_unused const gchar *log_domain,
- GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_level > G_LOG_LEVEL_WARNING)
- return;
-
- g_printerr("%s\n", message);
-}
+#include <stdio.h>
+#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc != 3) {
- g_printerr("Usage: read_conf FILE SETTING\n");
- return 1;
+ fprintf(stderr, "Usage: read_conf FILE SETTING\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
const char *name = argv[2];
- g_log_set_default_handler(my_log_func, NULL);
-
config_global_init();
Error error;
if (!ReadConfigFile(config_path, error)) {
- g_printerr("%s:", error.GetMessage());
- return 1;
+ LogError(error);
+ return EXIT_FAILURE;
}
ConfigOption option = ParseConfigOptionName(name);
@@ -63,11 +51,11 @@ int main(int argc, char **argv)
: nullptr;
int ret;
if (value != NULL) {
- g_print("%s\n", value);
- ret = 0;
+ printf("%s\n", value);
+ ret = EXIT_SUCCESS;
} else {
- g_printerr("No such setting: %s\n", name);
- ret = 2;
+ fprintf(stderr, "No such setting: %s\n", name);
+ ret = EXIT_FAILURE;
}
config_global_finish();
diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx
index 8426443ae..de77a00c4 100644
--- a/test/read_mixer.cxx
+++ b/test/read_mixer.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,82 +18,24 @@
*/
#include "config.h"
-#include "MixerControl.hxx"
-#include "MixerList.hxx"
-#include "FilterRegistry.hxx"
-#include "pcm/PcmVolume.hxx"
-#include "GlobalEvents.hxx"
+#include "mixer/MixerControl.hxx"
+#include "mixer/MixerList.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/Volume.hxx"
#include "Main.hxx"
#include "event/Loop.hxx"
-#include "ConfigData.hxx"
+#include "config/ConfigData.hxx"
#include "util/Error.hxx"
+#include "Log.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <string.h>
#include <unistd.h>
-EventLoop *main_loop;
-
-#ifdef HAVE_PULSE
-#include "output/PulseOutputPlugin.hxx"
-
-void
-pulse_output_lock(gcc_unused PulseOutput *po)
-{
-}
-
-void
-pulse_output_unlock(gcc_unused PulseOutput *po)
-{
-}
-
-void
-pulse_output_set_mixer(gcc_unused PulseOutput *po,
- gcc_unused PulseMixer *pm)
-{
-}
-
-void
-pulse_output_clear_mixer(gcc_unused PulseOutput *po,
- gcc_unused PulseMixer *pm)
-{
-}
-
-bool
-pulse_output_set_volume(gcc_unused PulseOutput *po,
- gcc_unused const struct pa_cvolume *volume,
- gcc_unused Error &error)
-{
- 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,
- gcc_unused unsigned volume)
-{
- return true;
-}
-
-#endif
-
-void
-GlobalEvents::Emit(gcc_unused Event event)
-{
-}
-
const struct filter_plugin *
filter_plugin_by_name(gcc_unused const char *name)
{
@@ -101,61 +43,53 @@ filter_plugin_by_name(gcc_unused const char *name)
return NULL;
}
-bool
-pcm_volume(gcc_unused void *buffer, gcc_unused size_t length,
- gcc_unused SampleFormat format,
- gcc_unused int volume)
-{
- assert(false);
- return false;
-}
-
int main(int argc, gcc_unused char **argv)
{
int volume;
if (argc != 2) {
- g_printerr("Usage: read_mixer PLUGIN\n");
- return 1;
+ fprintf(stderr, "Usage: read_mixer PLUGIN\n");
+ return EXIT_FAILURE;
}
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
+#endif
- main_loop = new EventLoop(EventLoop::Default());
+ EventLoop event_loop;
Error error;
- Mixer *mixer = mixer_new(&alsa_mixer_plugin, nullptr,
+ Mixer *mixer = mixer_new(event_loop, alsa_mixer_plugin,
+ *(AudioOutput *)nullptr,
+ *(MixerListener *)nullptr,
config_param(), error);
if (mixer == NULL) {
- g_printerr("mixer_new() failed: %s\n", error.GetMessage());
- return 2;
+ LogError(error, "mixer_new() failed");
+ return EXIT_FAILURE;
}
if (!mixer_open(mixer, error)) {
mixer_free(mixer);
- g_printerr("failed to open the mixer: %s\n", error.GetMessage());
- return 2;
+ LogError(error, "failed to open the mixer");
+ return EXIT_FAILURE;
}
volume = mixer_get_volume(mixer, error);
mixer_close(mixer);
mixer_free(mixer);
- delete main_loop;
-
assert(volume >= -1 && volume <= 100);
if (volume < 0) {
if (error.IsDefined()) {
- g_printerr("failed to read volume: %s\n",
- error.GetMessage());
+ LogError(error, "failed to read volume");
} else
- g_printerr("failed to read volume\n");
- return 2;
+ fprintf(stderr, "failed to read volume\n");
+ return EXIT_FAILURE;
}
- g_print("%d\n", volume);
+ printf("%d\n", volume);
return 0;
}
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index 52b2561b8..91ac9c674 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,11 +18,11 @@
*/
#include "config.h"
-#include "IOThread.hxx"
-#include "DecoderList.hxx"
-#include "DecoderPlugin.hxx"
-#include "InputInit.hxx"
-#include "InputStream.hxx"
+#include "ScopeIOThread.hxx"
+#include "decoder/DecoderList.hxx"
+#include "decoder/DecoderPlugin.hxx"
+#include "input/Init.hxx"
+#include "input/InputStream.hxx"
#include "AudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagId3.hxx"
@@ -32,11 +32,14 @@
#include "thread/Cond.hxx"
#include "Log.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
+#include <stdio.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
@@ -45,22 +48,22 @@
static bool empty = true;
static void
-print_duration(unsigned seconds, gcc_unused void *ctx)
+print_duration(SongTime duration, gcc_unused void *ctx)
{
- g_print("duration=%d\n", seconds);
+ printf("duration=%f\n", duration.ToDoubleS());
}
static void
print_tag(TagType type, const char *value, gcc_unused void *ctx)
{
- g_print("[%s]=%s\n", tag_item_names[type], value);
+ printf("[%s]=%s\n", tag_item_names[type], value);
empty = false;
}
static void
print_pair(const char *name, const char *value, gcc_unused void *ctx)
{
- g_print("\"%s\"=%s\n", name, value);
+ printf("\"%s\"=%s\n", name, value);
}
static const struct tag_handler print_handler = {
@@ -71,7 +74,7 @@ static const struct tag_handler print_handler = {
int main(int argc, char **argv)
{
- const char *decoder_name, *path;
+ const char *decoder_name;
const struct DecoderPlugin *plugin;
#ifdef HAVE_LOCALE_H
@@ -80,19 +83,20 @@ int main(int argc, char **argv)
#endif
if (argc != 3) {
- g_printerr("Usage: read_tags DECODER FILE\n");
- return 1;
+ fprintf(stderr, "Usage: read_tags DECODER FILE\n");
+ return EXIT_FAILURE;
}
decoder_name = argv[1];
- path = argv[2];
+ const Path path = Path::FromFS(argv[2]);
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
+#endif
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
Error error;
if (!input_stream_global_init(error)) {
@@ -104,8 +108,8 @@ int main(int argc, char **argv)
plugin = decoder_plugin_from_name(decoder_name);
if (plugin == NULL) {
- g_printerr("No such decoder: %s\n", decoder_name);
- return 1;
+ fprintf(stderr, "No such decoder: %s\n", decoder_name);
+ return EXIT_FAILURE;
}
bool success = plugin->ScanFile(path, print_handler, nullptr);
@@ -113,45 +117,30 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
- InputStream *is = InputStream::Open(path, mutex, cond,
- error);
+ InputStream *is = InputStream::OpenReady(path.c_str(),
+ mutex, cond,
+ error);
if (is == NULL) {
- g_printerr("Failed to open %s: %s\n",
- path, error.GetMessage());
- return 1;
- }
-
- mutex.lock();
-
- is->WaitReady();
-
- if (!is->Check(error)) {
- mutex.unlock();
-
- g_printerr("Failed to read %s: %s\n",
- path, error.GetMessage());
+ FormatError(error, "Failed to open %s", path.c_str());
return EXIT_FAILURE;
}
- mutex.unlock();
-
success = plugin->ScanStream(*is, print_handler, nullptr);
- is->Close();
+ delete is;
}
decoder_plugin_deinit_all();
input_stream_global_finish();
- io_thread_deinit();
if (!success) {
- g_printerr("Failed to read tags\n");
- return 1;
+ fprintf(stderr, "Failed to read tags\n");
+ return EXIT_FAILURE;
}
if (empty) {
- tag_ape_scan2(Path::FromFS(path), &print_handler, NULL);
+ tag_ape_scan2(path, &print_handler, NULL);
if (empty)
- tag_id3_scan(Path::FromFS(path), &print_handler, NULL);
+ tag_id3_scan(path, &print_handler, NULL);
}
return 0;
diff --git a/test/run_avahi.cxx b/test/run_avahi.cxx
index b392edb6d..b3b20365c 100644
--- a/test/run_avahi.cxx
+++ b/test/run_avahi.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,7 +20,7 @@
#include "config.h"
#include "event/Loop.hxx"
#include "ShutdownHandler.hxx"
-#include "ZeroconfAvahi.hxx"
+#include "zeroconf/ZeroconfAvahi.hxx"
#include <stdlib.h>
@@ -29,7 +29,7 @@ unsigned listen_port = 1234;
int
main(gcc_unused int argc, gcc_unused char **argv)
{
- EventLoop event_loop((EventLoop::Default()));
+ EventLoop event_loop;
const ShutdownHandler shutdown_handler(event_loop);
AvahiInit(event_loop, "test");
diff --git a/test/run_convert.cxx b/test/run_convert.cxx
index 0e873a3b3..8b9b15cf0 100644
--- a/test/run_convert.cxx
+++ b/test/run_convert.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,28 +27,18 @@
#include "AudioParser.hxx"
#include "AudioFormat.hxx"
#include "pcm/PcmConvert.hxx"
-#include "ConfigGlobal.hxx"
-#include "util/FifoBuffer.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/StaticFifoBuffer.hxx"
#include "util/Error.hxx"
+#include "Log.hxx"
#include "stdbin.h"
-#include <glib.h>
-
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
const char *
config_get_string(gcc_unused enum ConfigOption option,
const char *default_value)
@@ -59,29 +49,25 @@ config_get_string(gcc_unused enum ConfigOption option,
int main(int argc, char **argv)
{
AudioFormat in_audio_format, out_audio_format;
- const void *output;
if (argc != 3) {
- g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n");
+ fprintf(stderr,
+ "Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n");
return 1;
}
- g_log_set_default_handler(my_log_func, NULL);
-
Error error;
if (!audio_format_parse(in_audio_format, argv[1],
false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
AudioFormat out_audio_format_mask;
if (!audio_format_parse(out_audio_format_mask, argv[2],
true, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
out_audio_format = in_audio_format;
@@ -90,8 +76,12 @@ int main(int argc, char **argv)
const size_t in_frame_size = in_audio_format.GetFrameSize();
PcmConvert state;
+ if (!state.Open(in_audio_format, out_audio_format_mask, error)) {
+ LogError(error, "Failed to open PcmConvert");
+ return EXIT_FAILURE;
+ }
- FifoBuffer<uint8_t, 4096> buffer;
+ StaticFifoBuffer<uint8_t, 4096> buffer;
while (true) {
{
@@ -114,16 +104,18 @@ int main(int argc, char **argv)
buffer.Consume(src.size);
- size_t length;
- output = state.Convert(in_audio_format, src.data, src.size,
- out_audio_format, &length, error);
- if (output == NULL) {
- g_printerr("Failed to convert: %s\n", error.GetMessage());
- return 2;
+ auto output = state.Convert({src.data, src.size}, error);
+ if (output.IsNull()) {
+ state.Close();
+ LogError(error, "Failed to convert");
+ return EXIT_FAILURE;
}
- gcc_unused ssize_t ignored = write(1, output, length);
+ gcc_unused ssize_t ignored = write(1, output.data,
+ output.size);
}
+ state.Close();
+
return EXIT_SUCCESS;
}
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index 3fbfc5521..0e9af6a1a 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,239 +18,89 @@
*/
#include "config.h"
-#include "IOThread.hxx"
-#include "DecoderList.hxx"
-#include "DecoderAPI.hxx"
-#include "InputInit.hxx"
-#include "InputStream.hxx"
+#include "ScopeIOThread.hxx"
+#include "decoder/DecoderList.hxx"
+#include "decoder/DecoderPlugin.hxx"
+#include "FakeDecoderAPI.hxx"
+#include "input/Init.hxx"
+#include "input/InputStream.hxx"
+#include "fs/Path.hxx"
#include "AudioFormat.hxx"
#include "util/Error.hxx"
-#include "thread/Cond.hxx"
#include "Log.hxx"
#include "stdbin.h"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
-
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
-struct Decoder {
- const char *uri;
-
- const struct DecoderPlugin *plugin;
-
- bool initialized;
-};
-
-void
-decoder_initialized(Decoder &decoder,
- const AudioFormat audio_format,
- gcc_unused bool seekable,
- float duration)
-{
- struct audio_format_string af_string;
-
- assert(!decoder.initialized);
- assert(audio_format.IsValid());
-
- g_printerr("audio_format=%s duration=%f\n",
- audio_format_to_string(audio_format, &af_string),
- duration);
-
- decoder.initialized = true;
-}
-
-DecoderCommand
-decoder_get_command(gcc_unused Decoder &decoder)
-{
- return DecoderCommand::NONE;
-}
-
-void
-decoder_command_finished(gcc_unused Decoder &decoder)
-{
-}
-
-double
-decoder_seek_where(gcc_unused Decoder &decoder)
-{
- return 1.0;
-}
-
-void
-decoder_seek_error(gcc_unused Decoder &decoder)
-{
-}
-
-size_t
-decoder_read(gcc_unused Decoder *decoder,
- InputStream &is,
- void *buffer, size_t length)
-{
- return is.LockRead(buffer, length, IgnoreError());
-}
-
-bool
-decoder_read_full(Decoder *decoder, InputStream &is,
- void *_buffer, size_t size)
-{
- uint8_t *buffer = (uint8_t *)_buffer;
-
- while (size > 0) {
- size_t nbytes = decoder_read(decoder, is, buffer, size);
- if (nbytes == 0)
- return false;
-
- buffer += nbytes;
- size -= nbytes;
- }
-
- return true;
-}
-
-bool
-decoder_skip(Decoder *decoder, InputStream &is, size_t size)
-{
- while (size > 0) {
- char buffer[1024];
- size_t nbytes = decoder_read(decoder, is, buffer,
- std::min(sizeof(buffer), size));
- if (nbytes == 0)
- return false;
-
- size -= nbytes;
- }
-
- return true;
-}
-
-void
-decoder_timestamp(gcc_unused Decoder &decoder,
- gcc_unused double t)
-{
-}
-
-DecoderCommand
-decoder_data(gcc_unused Decoder &decoder,
- gcc_unused InputStream *is,
- const void *data, size_t datalen,
- gcc_unused uint16_t kbit_rate)
-{
- gcc_unused ssize_t nbytes = write(1, data, datalen);
- return DecoderCommand::NONE;
-}
-
-DecoderCommand
-decoder_tag(gcc_unused Decoder &decoder,
- gcc_unused InputStream *is,
- gcc_unused Tag &&tag)
-{
- return DecoderCommand::NONE;
-}
-
-void
-decoder_replay_gain(gcc_unused Decoder &decoder,
- const ReplayGainInfo *rgi)
-{
- const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
- if (tuple->IsDefined())
- g_printerr("replay_gain[album]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-
- tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
- if (tuple->IsDefined())
- g_printerr("replay_gain[track]: gain=%f peak=%f\n",
- tuple->gain, tuple->peak);
-}
-
-void
-decoder_mixramp(gcc_unused Decoder &decoder, MixRampInfo &&mix_ramp)
-{
- fprintf(stderr, "MixRamp: start='%s' end='%s'\n",
- mix_ramp.GetStart(), mix_ramp.GetEnd());
-}
+#include <stdio.h>
int main(int argc, char **argv)
{
- const char *decoder_name;
-
if (argc != 3) {
- g_printerr("Usage: run_decoder DECODER URI >OUT\n");
- return 1;
+ fprintf(stderr, "Usage: run_decoder DECODER URI >OUT\n");
+ return EXIT_FAILURE;
}
Decoder decoder;
- decoder_name = argv[1];
- decoder.uri = argv[2];
+ const char *const decoder_name = argv[1];
+ const char *const uri = argv[2];
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
+#endif
- g_log_set_default_handler(my_log_func, NULL);
-
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
Error error;
if (!input_stream_global_init(error)) {
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
decoder_plugin_init_all();
- decoder.plugin = decoder_plugin_from_name(decoder_name);
- if (decoder.plugin == NULL) {
- g_printerr("No such decoder: %s\n", decoder_name);
- return 1;
+ const DecoderPlugin *plugin = decoder_plugin_from_name(decoder_name);
+ if (plugin == nullptr) {
+ fprintf(stderr, "No such decoder: %s\n", decoder_name);
+ return EXIT_FAILURE;
}
- decoder.initialized = false;
-
- if (decoder.plugin->file_decode != NULL) {
- decoder.plugin->FileDecode(decoder, decoder.uri);
- } else if (decoder.plugin->stream_decode != NULL) {
- Mutex mutex;
- Cond cond;
-
+ if (plugin->file_decode != nullptr) {
+ plugin->FileDecode(decoder, Path::FromFS(uri));
+ } else if (plugin->stream_decode != nullptr) {
InputStream *is =
- InputStream::Open(decoder.uri, mutex, cond, error);
+ InputStream::OpenReady(uri, decoder.mutex,
+ decoder.cond, error);
if (is == NULL) {
if (error.IsDefined())
LogError(error);
else
- g_printerr("InputStream::Open() failed\n");
+ fprintf(stderr, "InputStream::Open() failed\n");
- return 1;
+ return EXIT_FAILURE;
}
- decoder.plugin->StreamDecode(decoder, *is);
+ plugin->StreamDecode(decoder, *is);
- is->Close();
+ delete is;
} else {
- g_printerr("Decoder plugin is not usable\n");
- return 1;
+ fprintf(stderr, "Decoder plugin is not usable\n");
+ return EXIT_FAILURE;
}
decoder_plugin_deinit_all();
input_stream_global_finish();
- io_thread_deinit();
if (!decoder.initialized) {
- g_printerr("Decoding failed\n");
- return 1;
+ fprintf(stderr, "Decoding failed\n");
+ return EXIT_FAILURE;
}
return 0;
diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx
index 838ee708e..f16d8cb0a 100644
--- a/test/run_encoder.cxx
+++ b/test/run_encoder.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,16 +18,17 @@
*/
#include "config.h"
-#include "EncoderList.hxx"
-#include "EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "encoder/EncoderPlugin.hxx"
#include "AudioFormat.hxx"
#include "AudioParser.hxx"
-#include "ConfigData.hxx"
+#include "config/ConfigData.hxx"
#include "util/Error.hxx"
+#include "Log.hxx"
#include "stdbin.h"
-#include <glib.h>
-
+#include <stdio.h>
+#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
@@ -50,8 +51,9 @@ int main(int argc, char **argv)
/* parse command line */
if (argc > 3) {
- g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n");
- return 1;
+ fprintf(stderr,
+ "Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n");
+ return EXIT_FAILURE;
}
if (argc > 1)
@@ -63,8 +65,8 @@ int main(int argc, char **argv)
const auto plugin = encoder_plugin_get(encoder_name);
if (plugin == NULL) {
- g_printerr("No such encoder: %s\n", encoder_name);
- return 1;
+ fprintf(stderr, "No such encoder: %s\n", encoder_name);
+ return EXIT_FAILURE;
}
config_param param;
@@ -73,9 +75,8 @@ int main(int argc, char **argv)
Error error;
const auto encoder = encoder_init(*plugin, param, error);
if (encoder == NULL) {
- g_printerr("Failed to initialize encoder: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to initialize encoder");
+ return EXIT_FAILURE;
}
/* open the encoder */
@@ -83,16 +84,14 @@ int main(int argc, char **argv)
AudioFormat audio_format(44100, SampleFormat::S16, 2);
if (argc > 2) {
if (!audio_format_parse(audio_format, argv[2], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
if (!encoder_open(encoder, audio_format, error)) {
- g_printerr("Failed to open encoder: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to open encoder");
+ return EXIT_FAILURE;
}
encoder_to_stdout(*encoder);
@@ -102,19 +101,20 @@ int main(int argc, char **argv)
ssize_t nbytes;
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
if (!encoder_write(encoder, buffer, nbytes, error)) {
- g_printerr("encoder_write() failed: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "encoder_write() failed");
+ return EXIT_FAILURE;
}
encoder_to_stdout(*encoder);
}
if (!encoder_end(encoder, error)) {
- g_printerr("encoder_flush() failed: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "encoder_flush() failed");
+ return EXIT_FAILURE;
}
encoder_to_stdout(*encoder);
+
+ encoder_close(encoder);
+ encoder_finish(encoder);
}
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index 085fc256b..ab99c9a1e 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,23 +18,29 @@
*/
#include "config.h"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
#include "fs/Path.hxx"
#include "AudioParser.hxx"
#include "AudioFormat.hxx"
-#include "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "pcm/PcmVolume.hxx"
-#include "MixerControl.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "pcm/Volume.hxx"
+#include "mixer/MixerControl.hxx"
#include "stdbin.h"
#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
#include "system/FatalError.hxx"
+#include "Log.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
#include <errno.h>
#include <unistd.h>
@@ -45,45 +51,20 @@ mixer_set_volume(gcc_unused Mixer *mixer,
return true;
}
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
-static const struct config_param *
-find_named_config_block(ConfigOption option, const char *name)
-{
- 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;
-
- param = find_named_config_block(CONF_AUDIO_FILTER, name);
+ const config_param *param =
+ config_find_block(CONF_AUDIO_FILTER, "name", name);
if (param == NULL) {
- g_printerr("No such configured filter: %s\n", name);
+ fprintf(stderr, "No such configured filter: %s\n", name);
return nullptr;
}
Error error;
Filter *filter = filter_configured_new(*param, error);
if (filter == NULL) {
- g_printerr("Failed to load filter: %s\n", error.GetMessage());
+ LogError(error, "Failed to load filter");
return NULL;
}
@@ -97,8 +78,8 @@ int main(int argc, char **argv)
char buffer[4096];
if (argc < 3 || argc > 4) {
- g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
- return 1;
+ fprintf(stderr, "Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
@@ -107,11 +88,11 @@ int main(int argc, char **argv)
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
/* read configuration file (mpd.conf) */
@@ -124,9 +105,8 @@ int main(int argc, char **argv)
if (argc > 3) {
Error error;
if (!audio_format_parse(audio_format, argv[3], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
@@ -134,44 +114,43 @@ int main(int argc, char **argv)
Filter *filter = load_filter(argv[2]);
if (filter == NULL)
- return 1;
+ return EXIT_FAILURE;
/* open the filter */
Error error;
const AudioFormat out_audio_format = filter->Open(audio_format, error);
if (!out_audio_format.IsDefined()) {
- g_printerr("Failed to open filter: %s\n", error.GetMessage());
+ LogError(error, "Failed to open filter");
delete filter;
- return 1;
+ return EXIT_FAILURE;
}
- g_printerr("audio_format=%s\n",
- audio_format_to_string(out_audio_format, &af_string));
+ fprintf(stderr, "audio_format=%s\n",
+ audio_format_to_string(out_audio_format, &af_string));
/* play */
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.GetMessage());
+ auto dest = filter->FilterPCM({(const void *)buffer, (size_t)nbytes},
+ error);
+ if (dest.IsNull()) {
+ LogError(error, "filter/Filter failed");
filter->Close();
delete filter;
- return 1;
+ return EXIT_FAILURE;
}
- nbytes = write(1, dest, length);
+ nbytes = write(1, dest.data, dest.size);
if (nbytes < 0) {
- g_printerr("Failed to write: %s\n", g_strerror(errno));
+ fprintf(stderr, "Failed to write: %s\n",
+ strerror(errno));
filter->Close();
delete filter;
return 1;
diff --git a/test/run_gunzip.cxx b/test/run_gunzip.cxx
new file mode 100644
index 000000000..51bdb532e
--- /dev/null
+++ b/test/run_gunzip.cxx
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "fs/io/GunzipReader.hxx"
+#include "fs/io/FileReader.hxx"
+#include "fs/io/StdioOutputStream.hxx"
+#include "util/Error.hxx"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static bool
+Copy(OutputStream &dest, Reader &src, Error &error)
+{
+ while (true) {
+ char buffer[4096];
+ size_t nbytes = src.Read(buffer, sizeof(buffer), error);
+ if (nbytes == 0)
+ return !error.IsDefined();
+
+ if (!dest.Write(buffer, nbytes, error))
+ return false;
+ }
+}
+
+static bool
+CopyGunzip(OutputStream &dest, Reader &_src, Error &error)
+{
+ GunzipReader src(_src, error);
+ return src.IsDefined() && Copy(dest, src, error);
+}
+
+static bool
+CopyGunzip(FILE *_dest, Path src_path, Error &error)
+{
+ StdioOutputStream dest(_dest);
+ FileReader src(src_path, error);
+ return src.IsDefined() && CopyGunzip(dest, src, error);
+}
+
+int
+main(int argc, gcc_unused char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: run_gunzip PATH\n");
+ return EXIT_FAILURE;
+ }
+
+ Path path = Path::FromFS(argv[1]);
+
+ Error error;
+ if (!CopyGunzip(stdout, path, error)) {
+ fprintf(stderr, "%s\n", error.GetMessage());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/run_gzip.cxx b/test/run_gzip.cxx
new file mode 100644
index 000000000..c52b32ac7
--- /dev/null
+++ b/test/run_gzip.cxx
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "fs/io/GzipOutputStream.hxx"
+#include "fs/io/StdioOutputStream.hxx"
+#include "util/Error.hxx"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static bool
+Copy(OutputStream &dest, int src, Error &error)
+{
+ while (true) {
+ char buffer[4096];
+ ssize_t nbytes = read(src, buffer, sizeof(buffer));
+ if (nbytes <= 0) {
+ if (nbytes < 0) {
+ error.SetErrno();
+ return false;
+ } else
+ return true;
+ }
+
+ if (!dest.Write(buffer, nbytes, error))
+ return false;
+ }
+}
+
+static bool
+CopyGzip(OutputStream &_dest, int src, Error &error)
+{
+ GzipOutputStream dest(_dest, error);
+ return dest.IsDefined() &&
+ Copy(dest, src, error) &&
+ dest.Flush(error);
+}
+
+static bool
+CopyGzip(FILE *_dest, int src, Error &error)
+{
+ StdioOutputStream dest(_dest);
+ return CopyGzip(dest, src, error);
+}
+
+int
+main(int argc, gcc_unused char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: run_gzip\n");
+ return EXIT_FAILURE;
+ }
+
+ Error error;
+ if (!CopyGzip(stdout, STDIN_FILENO, error)) {
+ fprintf(stderr, "%s\n", error.GetMessage());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx
index c57e6e9ef..df4046356 100644
--- a/test/run_inotify.cxx
+++ b/test/run_inotify.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,13 +19,11 @@
#include "config.h"
#include "ShutdownHandler.hxx"
-#include "InotifySource.hxx"
+#include "db/update/InotifySource.hxx"
#include "event/Loop.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <sys/inotify.h>
static constexpr unsigned IN_MASK =
@@ -39,7 +37,7 @@ static void
my_inotify_callback(gcc_unused int wd, unsigned mask,
const char *name, gcc_unused void *ctx)
{
- g_print("mask=0x%x name='%s'\n", mask, name);
+ printf("mask=0x%x name='%s'\n", mask, name);
}
int main(int argc, char **argv)
@@ -47,13 +45,13 @@ int main(int argc, char **argv)
const char *path;
if (argc != 2) {
- g_printerr("Usage: run_inotify PATH\n");
- return 1;
+ fprintf(stderr, "Usage: run_inotify PATH\n");
+ return EXIT_FAILURE;
}
path = argv[1];
- EventLoop event_loop((EventLoop::Default()));
+ EventLoop event_loop;
const ShutdownHandler shutdown_handler(event_loop);
Error error;
@@ -62,17 +60,18 @@ int main(int argc, char **argv)
nullptr, error);
if (source == NULL) {
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
int descriptor = source->Add(path, IN_MASK, error);
if (descriptor < 0) {
delete source;
LogError(error);
- return 2;
+ return EXIT_FAILURE;
}
event_loop.Run();
delete source;
+ return EXIT_SUCCESS;
}
diff --git a/test/run_input.cxx b/test/run_input.cxx
index 3817ed418..6864a5d64 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,69 +21,59 @@
#include "TagSave.hxx"
#include "stdbin.h"
#include "tag/Tag.hxx"
-#include "ConfigGlobal.hxx"
-#include "InputStream.hxx"
-#include "InputInit.hxx"
-#include "IOThread.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "input/InputStream.hxx"
+#include "input/Init.hxx"
+#include "ScopeIOThread.hxx"
#include "util/Error.hxx"
#include "thread/Cond.hxx"
#include "Log.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "fs/io/StdioOutputStream.hxx"
#ifdef ENABLE_ARCHIVE
-#include "ArchiveList.hxx"
+#include "archive/ArchiveList.hxx"
#endif
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdlib.h>
static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
+tag_save(FILE *file, const Tag &tag)
{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
+ StdioOutputStream sos(file);
+ BufferedOutputStream bos(sos);
+ tag_save(bos, tag);
+ bos.Flush();
}
static int
dump_input_stream(InputStream *is)
{
- Error error;
- char buffer[4096];
- size_t num_read;
- ssize_t num_written;
-
is->Lock();
- /* wait until the stream becomes ready */
-
- is->WaitReady();
-
- if (!is->Check(error)) {
- LogError(error);
- is->Unlock();
- return EXIT_FAILURE;
- }
-
/* print meta data */
- if (!is->mime.empty())
- g_printerr("MIME type: %s\n", is->mime.c_str());
+ if (is->HasMimeType())
+ fprintf(stderr, "MIME type: %s\n", is->GetMimeType());
/* read data and tags from the stream */
while (!is->IsEOF()) {
Tag *tag = is->ReadTag();
if (tag != NULL) {
- g_printerr("Received a tag:\n");
+ fprintf(stderr, "Received a tag:\n");
tag_save(stderr, *tag);
delete tag;
}
- num_read = is->Read(buffer, sizeof(buffer), error);
+ Error error;
+ char buffer[4096];
+ size_t num_read = is->Read(buffer, sizeof(buffer), error);
if (num_read == 0) {
if (error.IsDefined())
LogError(error);
@@ -91,11 +81,12 @@ dump_input_stream(InputStream *is)
break;
}
- num_written = write(1, buffer, num_read);
+ ssize_t num_written = write(1, buffer, num_read);
if (num_written <= 0)
break;
}
+ Error error;
if (!is->Check(error)) {
LogError(error);
is->Unlock();
@@ -109,34 +100,30 @@ dump_input_stream(InputStream *is)
int main(int argc, char **argv)
{
- Error error;
- InputStream *is;
- int ret;
-
if (argc != 2) {
- g_printerr("Usage: run_input URI\n");
- return 1;
+ fprintf(stderr, "Usage: run_input URI\n");
+ return EXIT_FAILURE;
}
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
/* initialize MPD */
config_global_init();
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
#endif
+ Error error;
if (!input_stream_global_init(error)) {
LogError(error);
return 2;
@@ -147,16 +134,17 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
- is = InputStream::Open(argv[1], mutex, cond, error);
+ InputStream *is = InputStream::OpenReady(argv[1], mutex, cond, error);
+ int ret;
if (is != NULL) {
ret = dump_input_stream(is);
- is->Close();
+ delete is;
} else {
if (error.IsDefined())
LogError(error);
else
- g_printerr("input_stream::Open() failed\n");
- ret = 2;
+ fprintf(stderr, "input_stream::Open() failed\n");
+ ret = EXIT_FAILURE;
}
/* deinitialize everything */
@@ -167,8 +155,6 @@ int main(int argc, char **argv)
archive_plugin_deinit_all();
#endif
- io_thread_deinit();
-
config_global_finish();
return ret;
diff --git a/test/run_neighbor_explorer.cxx b/test/run_neighbor_explorer.cxx
new file mode 100644
index 000000000..c79948d6e
--- /dev/null
+++ b/test/run_neighbor_explorer.cxx
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "config/ConfigGlobal.hxx"
+#include "neighbor/Listener.hxx"
+#include "neighbor/Info.hxx"
+#include "neighbor/Glue.hxx"
+#include "fs/Path.hxx"
+#include "event/Loop.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+class MyNeighborListener final : public NeighborListener {
+ public:
+ /* virtual methods from class NeighborListener */
+ virtual void FoundNeighbor(const NeighborInfo &info) override {
+ printf("found '%s' (%s)\n",
+ info.display_name.c_str(), info.uri.c_str());
+ }
+
+ virtual void LostNeighbor(const NeighborInfo &info) override {
+ printf("lost '%s' (%s)\n",
+ info.display_name.c_str(), info.uri.c_str());
+ }
+};
+
+int
+main(int argc, char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: run_neighbor_explorer CONFIG\n");
+ return EXIT_FAILURE;
+ }
+
+ const Path config_path = Path::FromFS(argv[1]);
+
+ /* read configuration file (mpd.conf) */
+
+ Error error;
+
+ config_global_init();
+ if (!ReadConfigFile(config_path, error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+
+ /* initialize the core */
+
+ EventLoop loop;
+
+ /* initialize neighbor plugins */
+
+ MyNeighborListener listener;
+ NeighborGlue neighbor;
+ if (!neighbor.Init(loop, listener, error) || !neighbor.Open(error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+
+ /* run */
+
+ loop.Run();
+ neighbor.Close();
+ return EXIT_SUCCESS;
+}
diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx
index 3193fefd2..9a361b790 100644
--- a/test/run_normalize.cxx
+++ b/test/run_normalize.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -30,9 +30,8 @@
#include "util/Error.hxx"
#include "stdbin.h"
-#include <glib.h>
-
#include <stddef.h>
+#include <stdio.h>
#include <unistd.h>
#include <string.h>
@@ -43,7 +42,7 @@ int main(int argc, char **argv)
ssize_t nbytes;
if (argc > 2) {
- g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n");
+ fprintf(stderr, "Usage: run_normalize [FORMAT] <IN >OUT\n");
return 1;
}
@@ -51,7 +50,7 @@ int main(int argc, char **argv)
if (argc > 1) {
Error error;
if (!audio_format_parse(audio_format, argv[1], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
+ fprintf(stderr, "Failed to parse audio format: %s\n",
error.GetMessage());
return 1;
}
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 7982bd7de..345127556 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,38 +18,33 @@
*/
#include "config.h"
-#include "OutputControl.hxx"
-#include "OutputInternal.hxx"
-#include "OutputPlugin.hxx"
-#include "ConfigData.hxx"
-#include "ConfigGlobal.hxx"
-#include "ConfigOption.hxx"
+#include "output/Internal.hxx"
+#include "output/OutputPlugin.hxx"
+#include "config/ConfigData.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "config/ConfigOption.hxx"
#include "Idle.hxx"
#include "Main.hxx"
#include "event/Loop.hxx"
-#include "GlobalEvents.hxx"
-#include "IOThread.hxx"
+#include "ScopeIOThread.hxx"
#include "fs/Path.hxx"
#include "AudioParser.hxx"
#include "pcm/PcmConvert.hxx"
-#include "FilterRegistry.hxx"
+#include "filter/FilterRegistry.hxx"
#include "PlayerControl.hxx"
#include "stdbin.h"
#include "util/Error.hxx"
+#include "Log.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
-
-EventLoop *main_loop;
-
-void
-GlobalEvents::Emit(gcc_unused Event event)
-{
-}
+#include <stdio.h>
const struct filter_plugin *
filter_plugin_by_name(gcc_unused const char *name)
@@ -58,68 +53,61 @@ filter_plugin_by_name(gcc_unused const char *name)
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;
-}
-
-PlayerControl::PlayerControl(gcc_unused unsigned _buffer_chunks,
- gcc_unused unsigned _buffered_before_play) {}
+PlayerControl::PlayerControl(PlayerListener &_listener,
+ MultipleOutputs &_outputs,
+ unsigned _buffer_chunks,
+ unsigned _buffered_before_play)
+ :listener(_listener), outputs(_outputs),
+ buffer_chunks(_buffer_chunks),
+ buffered_before_play(_buffered_before_play) {}
PlayerControl::~PlayerControl() {}
-static struct audio_output *
-load_audio_output(const char *name)
+static AudioOutput *
+load_audio_output(EventLoop &event_loop, const char *name)
{
- const struct config_param *param;
-
- param = find_named_config_block(CONF_AUDIO_OUTPUT, name);
+ const config_param *param =
+ config_find_block(CONF_AUDIO_OUTPUT, "name", name);
if (param == NULL) {
- g_printerr("No such configured audio output: %s\n", name);
+ fprintf(stderr, "No such configured audio output: %s\n", name);
return nullptr;
}
- static struct PlayerControl dummy_player_control(32, 4);
+ static struct PlayerControl dummy_player_control(*(PlayerListener *)nullptr,
+ *(MultipleOutputs *)nullptr,
+ 32, 4);
Error error;
- struct audio_output *ao =
- audio_output_new(*param, dummy_player_control, error);
+ AudioOutput *ao =
+ audio_output_new(event_loop, *param,
+ *(MixerListener *)nullptr,
+ dummy_player_control,
+ error);
if (ao == nullptr)
- g_printerr("%s\n", error.GetMessage());
+ LogError(error);
return ao;
}
static bool
-run_output(struct audio_output *ao, AudioFormat audio_format)
+run_output(AudioOutput *ao, AudioFormat audio_format)
{
/* open the audio output */
Error error;
if (!ao_plugin_enable(ao, error)) {
- g_printerr("Failed to enable audio output: %s\n",
- error.GetMessage());
+ LogError(error, "Failed to enable audio output");
return false;
}
if (!ao_plugin_open(ao, audio_format, error)) {
ao_plugin_disable(ao);
- g_printerr("Failed to open audio output: %s\n",
- error.GetMessage());
+ LogError(error, "Failed to open audio output");
return false;
}
struct audio_format_string af_string;
- g_printerr("audio_format=%s\n",
- audio_format_to_string(audio_format, &af_string));
+ fprintf(stderr, "audio_format=%s\n",
+ audio_format_to_string(audio_format, &af_string));
size_t frame_size = audio_format.GetFrameSize();
@@ -145,8 +133,7 @@ run_output(struct audio_output *ao, AudioFormat audio_format)
if (consumed == 0) {
ao_plugin_close(ao);
ao_plugin_disable(ao);
- g_printerr("Failed to play: %s\n",
- error.GetMessage());
+ LogError(error, "Failed to play");
return false;
}
@@ -168,34 +155,35 @@ int main(int argc, char **argv)
Error error;
if (argc < 3 || argc > 4) {
- g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n");
- return 1;
+ fprintf(stderr, "Usage: run_output CONFIG NAME [FORMAT] <IN\n");
+ return EXIT_FAILURE;
}
const Path config_path = Path::FromFS(argv[1]);
AudioFormat audio_format(44100, SampleFormat::S16, 2);
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
+#endif
/* read configuration file (mpd.conf) */
config_global_init();
if (!ReadConfigFile(config_path, error)) {
- g_printerr("%s\n", error.GetMessage());
- return 1;
+ LogError(error);
+ return EXIT_FAILURE;
}
- main_loop = new EventLoop(EventLoop::Default());
+ EventLoop event_loop;
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
/* initialize the audio output */
- struct audio_output *ao = load_audio_output(argv[2]);
+ AudioOutput *ao = load_audio_output(event_loop, argv[2]);
if (ao == NULL)
return 1;
@@ -203,9 +191,8 @@ int main(int argc, char **argv)
if (argc > 3) {
if (!audio_format_parse(audio_format, argv[3], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
@@ -217,10 +204,6 @@ int main(int argc, char **argv)
audio_output_free(ao);
- io_thread_deinit();
-
- delete main_loop;
-
config_global_finish();
return success ? EXIT_SUCCESS : EXIT_FAILURE;
diff --git a/test/run_resolver.cxx b/test/run_resolver.cxx
index 7da2fd5b2..71cadbeec 100644
--- a/test/run_resolver.cxx
+++ b/test/run_resolver.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,8 +22,6 @@
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#ifdef WIN32
#include <ws2tcpip.h>
#include <winsock.h>
@@ -32,12 +30,13 @@
#include <netdb.h>
#endif
+#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc != 2) {
- g_printerr("Usage: run_resolver HOST\n");
+ fprintf(stderr, "Usage: run_resolver HOST\n");
return EXIT_FAILURE;
}
@@ -51,16 +50,8 @@ int main(int argc, char **argv)
}
for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) {
- char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen,
- error);
- if (p == NULL) {
- freeaddrinfo(ai);
- LogError(error);
- return EXIT_FAILURE;
- }
-
- g_print("%s\n", p);
- g_free(p);
+ const auto s = sockaddr_to_string(i->ai_addr, i->ai_addrlen);
+ printf("%s\n", s.c_str());
}
freeaddrinfo(ai);
diff --git a/test/run_storage.cxx b/test/run_storage.cxx
new file mode 100644
index 000000000..9fc6e6e76
--- /dev/null
+++ b/test/run_storage.cxx
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ScopeIOThread.hxx"
+#include "storage/Registry.hxx"
+#include "storage/StorageInterface.hxx"
+#include "storage/FileInfo.hxx"
+#include "util/Error.hxx"
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
+#include <memory>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static Storage *
+MakeStorage(const char *uri)
+{
+ Error error;
+ Storage *storage = CreateStorageURI(io_thread_get(), uri, error);
+ if (storage == nullptr) {
+ fprintf(stderr, "%s\n", error.GetMessage());
+ exit(EXIT_FAILURE);
+ }
+
+ return storage;
+}
+
+static int
+Ls(Storage &storage, const char *path)
+{
+ Error error;
+ auto dir = storage.OpenDirectory(path, error);
+ if (dir == nullptr) {
+ fprintf(stderr, "%s\n", error.GetMessage());
+ return EXIT_FAILURE;
+ }
+
+ const char *name;
+ while ((name = dir->Read()) != nullptr) {
+ FileInfo info;
+ if (!dir->GetInfo(false, info, error)) {
+ printf("Error on %s: %s\n", name, error.GetMessage());
+ error.Clear();
+ continue;
+ }
+
+ const char *type = "unk";
+ switch (info.type) {
+ case FileInfo::Type::OTHER:
+ type = "oth";
+ break;
+
+ case FileInfo::Type::REGULAR:
+ type = "reg";
+ break;
+
+ case FileInfo::Type::DIRECTORY:
+ type = "dir";
+ break;
+ }
+
+ char mtime[32];
+ strftime(mtime, sizeof(mtime), "%F", gmtime(&info.mtime));
+
+ printf("%s %10llu %s %s\n",
+ type, (unsigned long long)info.size,
+ mtime, name);
+ }
+
+ delete dir;
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 3) {
+ fprintf(stderr, "Usage: run_storage COMMAND URI ...\n");
+ return EXIT_FAILURE;
+ }
+
+ /* initialize GLib */
+
+#ifdef HAVE_GLIB
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+#endif
+
+ const char *const command = argv[1];
+ const char *const storage_uri = argv[2];
+
+ const ScopeIOThread io_thread;
+
+ if (strcmp(command, "ls") == 0) {
+ if (argc != 4) {
+ fprintf(stderr, "Usage: run_storage ls URI PATH\n");
+ return EXIT_FAILURE;
+ }
+
+ const char *const path = argv[3];
+
+ std::unique_ptr<Storage> storage(MakeStorage(storage_uri));
+
+ return Ls(*storage, path);
+ } else {
+ fprintf(stderr, "Unknown command\n");
+ return EXIT_FAILURE;
+ }
+}
diff --git a/test/software_volume.cxx b/test/software_volume.cxx
index 19a0be88c..1e41f95fd 100644
--- a/test/software_volume.cxx
+++ b/test/software_volume.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,15 +24,17 @@
*/
#include "config.h"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
#include "AudioParser.hxx"
#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "stdbin.h"
+#include "Log.hxx"
-#include <glib.h>
-
+#include <stdio.h>
#include <stddef.h>
+#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
@@ -41,28 +43,29 @@ int main(int argc, char **argv)
ssize_t nbytes;
if (argc > 2) {
- g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n");
- return 1;
+ fprintf(stderr, "Usage: software_volume [FORMAT] <IN >OUT\n");
+ return EXIT_FAILURE;
}
Error error;
AudioFormat audio_format(48000, SampleFormat::S16, 2);
if (argc > 1) {
if (!audio_format_parse(audio_format, argv[1], false, error)) {
- g_printerr("Failed to parse audio format: %s\n",
- error.GetMessage());
- return 1;
+ LogError(error, "Failed to parse audio format");
+ return EXIT_FAILURE;
}
}
- while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
- if (!pcm_volume(buffer, nbytes,
- audio_format.format,
- PCM_VOLUME_1 / 2)) {
- g_printerr("pcm_volume() has failed\n");
- return 2;
- }
+ PcmVolume pv;
+ if (!pv.Open(audio_format.format, error)) {
+ fprintf(stderr, "%s\n", error.GetMessage());
+ return EXIT_FAILURE;
+ }
- gcc_unused ssize_t ignored = write(1, buffer, nbytes);
+ while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
+ auto dest = pv.Apply({buffer, size_t(nbytes)});
+ gcc_unused ssize_t ignored = write(1, dest.data, dest.size);
}
+
+ pv.Close();
}
diff --git a/test/stdbin.h b/test/stdbin.h
index 48cac7338..8b5502e6f 100644
--- a/test/stdbin.h
+++ b/test/stdbin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/test_archive.cxx b/test/test_archive.cxx
index dbb41fe42..1b15e306f 100644
--- a/test/test_archive.cxx
+++ b/test/test_archive.cxx
@@ -1,5 +1,5 @@
#include "config.h"
-#include "ArchiveLookup.hxx"
+#include "archive/ArchiveLookup.hxx"
#include "Compiler.h"
#include <cppunit/TestFixture.h>
@@ -7,8 +7,6 @@
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
-#include <glib.h>
-
#include <string.h>
#include <stdlib.h>
@@ -29,22 +27,22 @@ ArchiveLookupTest::TestArchiveLookup()
char *path = strdup("");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
- g_free(path);
+ free(path);
path = strdup(".");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
- g_free(path);
+ free(path);
path = strdup("config.h");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
- g_free(path);
+ free(path);
path = strdup("src/foo/bar");
CPPUNIT_ASSERT_EQUAL(false,
archive_lookup(path, &archive, &inpath, &suffix));
- g_free(path);
+ free(path);
path = strdup("Makefile/foo/bar");
CPPUNIT_ASSERT_EQUAL(true,
@@ -53,7 +51,7 @@ ArchiveLookupTest::TestArchiveLookup()
CPPUNIT_ASSERT_EQUAL(0, strcmp(archive, "Makefile"));
CPPUNIT_ASSERT_EQUAL(0, strcmp(inpath, "foo/bar"));
CPPUNIT_ASSERT_EQUAL((const char *)nullptr, suffix);
- g_free(path);
+ free(path);
path = strdup("config.h/foo/bar");
CPPUNIT_ASSERT_EQUAL(true,
@@ -62,7 +60,7 @@ ArchiveLookupTest::TestArchiveLookup()
CPPUNIT_ASSERT_EQUAL(0, strcmp(archive, "config.h"));
CPPUNIT_ASSERT_EQUAL(0, strcmp(inpath, "foo/bar"));
CPPUNIT_ASSERT_EQUAL(0, strcmp(suffix, "h"));
- g_free(path);
+ free(path);
}
CPPUNIT_TEST_SUITE_REGISTRATION(ArchiveLookupTest);
diff --git a/test/test_byte_reverse.cxx b/test/test_byte_reverse.cxx
index 58673e4f8..0ab97e4d1 100644
--- a/test/test_byte_reverse.cxx
+++ b/test/test_byte_reverse.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/test/test_icy_parser.cxx b/test/test_icy_parser.cxx
index 2abf60f9e..a996df134 100644
--- a/test/test_icy_parser.cxx
+++ b/test/test_icy_parser.cxx
@@ -28,7 +28,7 @@ icy_parse_tag(const char *p)
static void
CompareTagTitle(const Tag &tag, const std::string &title)
{
- CPPUNIT_ASSERT_EQUAL(1u, tag.num_items);
+ CPPUNIT_ASSERT_EQUAL(uint16_t(1), tag.num_items);
const TagItem &item = *tag.items[0];
CPPUNIT_ASSERT_EQUAL(TAG_TITLE, item.type);
@@ -47,7 +47,7 @@ static void
TestIcyParserEmpty(const char *input)
{
Tag *tag = icy_parse_tag(input);
- CPPUNIT_ASSERT_EQUAL(0u, tag->num_items);
+ CPPUNIT_ASSERT_EQUAL(uint16_t(0), tag->num_items);
delete tag;
}
diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx
index 2a0aa8628..7cdd8b63f 100644
--- a/test/test_pcm_all.hxx
+++ b/test/test_pcm_all.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -34,8 +34,6 @@ public:
void TestDither32();
};
-CPPUNIT_TEST_SUITE_REGISTRATION(PcmDitherTest);
-
class PcmPackTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(PcmPackTest);
CPPUNIT_TEST(TestPack24);
@@ -47,8 +45,6 @@ public:
void TestUnpack24();
};
-CPPUNIT_TEST_SUITE_REGISTRATION(PcmPackTest);
-
class PcmChannelsTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(PcmChannelsTest);
CPPUNIT_TEST(TestChannels16);
@@ -60,8 +56,6 @@ public:
void TestChannels32();
};
-CPPUNIT_TEST_SUITE_REGISTRATION(PcmChannelsTest);
-
class PcmVolumeTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(PcmVolumeTest);
CPPUNIT_TEST(TestVolume8);
@@ -79,8 +73,6 @@ public:
void TestVolumeFloat();
};
-CPPUNIT_TEST_SUITE_REGISTRATION(PcmVolumeTest);
-
class PcmFormatTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(PcmFormatTest);
CPPUNIT_TEST(TestFormat8to16);
@@ -96,8 +88,6 @@ public:
void TestFormatFloat();
};
-CPPUNIT_TEST_SUITE_REGISTRATION(PcmFormatTest);
-
class PcmMixTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(PcmMixTest);
CPPUNIT_TEST(TestMix8);
@@ -113,6 +103,19 @@ public:
void TestMix32();
};
-CPPUNIT_TEST_SUITE_REGISTRATION(PcmMixTest);
+class PcmExportTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(PcmExportTest);
+ CPPUNIT_TEST(TestShift8);
+ CPPUNIT_TEST(TestPack24);
+ CPPUNIT_TEST(TestReverseEndian);
+ CPPUNIT_TEST(TestDop);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestShift8();
+ void TestPack24();
+ void TestReverseEndian();
+ void TestDop();
+};
#endif
diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx
index 85c872674..748a76351 100644
--- a/test/test_pcm_channels.cxx
+++ b/test/test_pcm_channels.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,33 +22,30 @@
#include "test_pcm_util.hxx"
#include "pcm/PcmChannels.hxx"
#include "pcm/PcmBuffer.hxx"
+#include "util/ConstBuffer.hxx"
void
PcmChannelsTest::TestChannels16()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 509;
const auto src = TestDataBuffer<int16_t, N * 2>();
PcmBuffer buffer;
/* stereo to mono */
- size_t dest_size;
- const int16_t *dest =
- pcm_convert_channels_16(buffer, 1, 2, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) / 2, dest_size);
+ auto dest = pcm_convert_channels_16(buffer, 1, 2, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N, dest.size);
for (unsigned i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int16_t((src[i * 2] + src[i * 2 + 1]) / 2),
dest[i]);
/* mono to stereo */
- dest = pcm_convert_channels_16(buffer, 2, 1, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) * 2, dest_size);
+ dest = pcm_convert_channels_16(buffer, 2, 1, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N * 4, dest.size);
for (unsigned i = 0; i < N; ++i) {
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
@@ -58,29 +55,25 @@ PcmChannelsTest::TestChannels16()
void
PcmChannelsTest::TestChannels32()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 509;
const auto src = TestDataBuffer<int32_t, N * 2>();
PcmBuffer buffer;
/* stereo to mono */
- size_t dest_size;
- const int32_t *dest =
- pcm_convert_channels_32(buffer, 1, 2, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) / 2, dest_size);
+ auto dest = pcm_convert_channels_32(buffer, 1, 2, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N, dest.size);
for (unsigned i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int32_t(((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2),
dest[i]);
/* mono to stereo */
- dest = pcm_convert_channels_32(buffer, 2, 1, src, sizeof(src),
- &dest_size);
- CPPUNIT_ASSERT(dest != NULL);
- CPPUNIT_ASSERT_EQUAL(sizeof(src) * 2, dest_size);
+ dest = pcm_convert_channels_32(buffer, 2, 1, { src, N * 2 });
+ CPPUNIT_ASSERT(!dest.IsNull());
+ CPPUNIT_ASSERT_EQUAL(N * 4, dest.size);
for (unsigned i = 0; i < N; ++i) {
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]);
CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]);
diff --git a/test/test_pcm_dither.cxx b/test/test_pcm_dither.cxx
index 710deffcc..09a2b5cf9 100644
--- a/test/test_pcm_dither.cxx
+++ b/test/test_pcm_dither.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,12 @@
#include "test_pcm_all.hxx"
#include "test_pcm_util.hxx"
-#include "pcm/PcmDither.hxx"
+#include "pcm/PcmDither.cxx"
void
PcmDitherTest::TestDither24()
{
- constexpr unsigned N = 256;
+ constexpr unsigned N = 509;
const auto src = TestDataBuffer<int32_t, N>(RandomInt24());
int16_t dest[N];
@@ -40,7 +40,7 @@ PcmDitherTest::TestDither24()
void
PcmDitherTest::TestDither32()
{
- constexpr unsigned N = 256;
+ constexpr unsigned N = 509;
const auto src = TestDataBuffer<int32_t, N>();
int16_t dest[N];
diff --git a/test/test_pcm_export.cxx b/test/test_pcm_export.cxx
new file mode 100644
index 000000000..410e64e4d
--- /dev/null
+++ b/test/test_pcm_export.cxx
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "test_pcm_all.hxx"
+#include "pcm/PcmExport.hxx"
+#include "system/ByteOrder.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <string.h>
+
+void
+PcmExportTest::TestShift8()
+{
+ static constexpr int32_t src[] = { 0x0, 0x1, 0x100, 0x10000, 0xffffff };
+ static constexpr uint32_t expected[] = { 0x0, 0x100, 0x10000, 0x1000000, 0xffffff00 };
+
+ PcmExport e;
+ e.Open(SampleFormat::S24_P32, 2, false, true, false, false);
+
+ auto dest = e.Export({src, sizeof(src)});
+ CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size);
+ CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0);
+}
+
+void
+PcmExportTest::TestPack24()
+{
+ static constexpr int32_t src[] = { 0x0, 0x1, 0x100, 0x10000, 0xffffff };
+
+ static constexpr uint8_t expected_be[] = {
+ 0, 0, 0x0,
+ 0, 0, 0x1,
+ 0, 0x1, 0x00,
+ 0x1, 0x00, 0x00,
+ 0xff, 0xff, 0xff,
+ };
+
+ static constexpr uint8_t expected_le[] = {
+ 0, 0, 0x0,
+ 0x1, 0, 0,
+ 0x00, 0x1, 0,
+ 0, 0x00, 0x01,
+ 0xff, 0xff, 0xff,
+ };
+
+ static constexpr size_t expected_size = sizeof(expected_be);
+ static const uint8_t *const expected = IsBigEndian()
+ ? expected_be : expected_le;
+
+ PcmExport e;
+ e.Open(SampleFormat::S24_P32, 2, false, false, true, false);
+
+ auto dest = e.Export({src, sizeof(src)});
+ CPPUNIT_ASSERT_EQUAL(expected_size, dest.size);
+ CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0);
+}
+
+void
+PcmExportTest::TestReverseEndian()
+{
+ static constexpr uint8_t src[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
+ };
+
+ static constexpr uint8_t expected2[] = {
+ 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11
+ };
+
+ static constexpr uint8_t expected4[] = {
+ 4, 3, 2, 1, 8, 7, 6, 5, 12, 11, 10, 9,
+ };
+
+ PcmExport e;
+ e.Open(SampleFormat::S8, 2, false, false, false, true);
+
+ auto dest = e.Export({src, sizeof(src)});
+ CPPUNIT_ASSERT_EQUAL(sizeof(src), dest.size);
+ CPPUNIT_ASSERT(memcmp(dest.data, src, dest.size) == 0);
+
+ e.Open(SampleFormat::S16, 2, false, false, false, true);
+ dest = e.Export({src, sizeof(src)});
+ CPPUNIT_ASSERT_EQUAL(sizeof(expected2), dest.size);
+ CPPUNIT_ASSERT(memcmp(dest.data, expected2, dest.size) == 0);
+
+ e.Open(SampleFormat::S32, 2, false, false, false, true);
+ dest = e.Export({src, sizeof(src)});
+ CPPUNIT_ASSERT_EQUAL(sizeof(expected4), dest.size);
+ CPPUNIT_ASSERT(memcmp(dest.data, expected4, dest.size) == 0);
+}
+
+void
+PcmExportTest::TestDop()
+{
+ static constexpr uint8_t src[] = {
+ 0x01, 0x23, 0x45, 0x67,
+ 0x89, 0xab, 0xcd, 0xef,
+ };
+
+ static constexpr uint32_t expected[] = {
+ 0xff050145,
+ 0xff052367,
+ 0xfffa89cd,
+ 0xfffaabef,
+ };
+
+ PcmExport e;
+ e.Open(SampleFormat::DSD, 2, true, false, false, false);
+
+ auto dest = e.Export({src, sizeof(src)});
+ CPPUNIT_ASSERT_EQUAL(sizeof(expected), dest.size);
+ CPPUNIT_ASSERT(memcmp(dest.data, expected, dest.size) == 0);
+}
diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx
index 49f4ccd4b..825a8bd84 100644
--- a/test/test_pcm_format.cxx
+++ b/test/test_pcm_format.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,17 +29,14 @@
void
PcmFormatTest::TestFormat8to16()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 509;
const auto src = TestDataBuffer<int8_t, N>();
PcmBuffer buffer;
- size_t d_size;
PcmDither dither;
- auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8,
- src, sizeof(src), &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8, src);
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 8);
@@ -48,16 +45,13 @@ PcmFormatTest::TestFormat8to16()
void
PcmFormatTest::TestFormat16to24()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 509;
const auto src = TestDataBuffer<int16_t, N>();
PcmBuffer buffer;
- size_t d_size;
- auto d = pcm_convert_to_24(buffer, SampleFormat::S16,
- src, sizeof(src), &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ auto d = pcm_convert_to_24(buffer, SampleFormat::S16, src);
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 8);
@@ -66,16 +60,13 @@ PcmFormatTest::TestFormat16to24()
void
PcmFormatTest::TestFormat16to32()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 509;
const auto src = TestDataBuffer<int16_t, N>();
PcmBuffer buffer;
- size_t d_size;
- auto d = pcm_convert_to_32(buffer, SampleFormat::S16,
- src, sizeof(src), &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ auto d = pcm_convert_to_32(buffer, SampleFormat::S16, src);
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 16);
@@ -84,31 +75,46 @@ PcmFormatTest::TestFormat16to32()
void
PcmFormatTest::TestFormatFloat()
{
- constexpr unsigned N = 256;
+ constexpr size_t N = 509;
const auto src = TestDataBuffer<int16_t, N>();
PcmBuffer buffer1, buffer2;
- size_t f_size;
- auto f = pcm_convert_to_float(buffer1, SampleFormat::S16,
- src, sizeof(src), &f_size);
- auto f_end = pcm_end_pointer(f, f_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(f_end - f));
+ auto f = pcm_convert_to_float(buffer1, SampleFormat::S16, src);
+ CPPUNIT_ASSERT_EQUAL(N, f.size);
- for (auto i = f; i != f_end; ++i) {
- CPPUNIT_ASSERT(*i >= -1.);
- CPPUNIT_ASSERT(*i <= 1.);
+ for (size_t i = 0; i != f.size; ++i) {
+ CPPUNIT_ASSERT(f[i] >= -1.);
+ CPPUNIT_ASSERT(f[i] <= 1.);
}
PcmDither dither;
- size_t d_size;
auto d = pcm_convert_to_16(buffer2, dither,
SampleFormat::FLOAT,
- f, f_size, &d_size);
- auto d_end = pcm_end_pointer(d, d_size);
- CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d));
+ f.ToVoid());
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
for (size_t i = 0; i < N; ++i)
CPPUNIT_ASSERT_EQUAL(src[i], d[i]);
+
+ /* check if clamping works */
+ float *writable = const_cast<float *>(f.data);
+ *writable++ = 1.01;
+ *writable++ = 10;
+ *writable++ = -1.01;
+ *writable++ = -10;
+
+ d = pcm_convert_to_16(buffer2, dither,
+ SampleFormat::FLOAT,
+ f.ToVoid());
+ CPPUNIT_ASSERT_EQUAL(N, d.size);
+
+ CPPUNIT_ASSERT_EQUAL(32767, int(d[0]));
+ CPPUNIT_ASSERT_EQUAL(32767, int(d[1]));
+ CPPUNIT_ASSERT_EQUAL(-32768, int(d[2]));
+ CPPUNIT_ASSERT_EQUAL(-32768, int(d[3]));
+
+ for (size_t i = 4; i < N; ++i)
+ CPPUNIT_ASSERT_EQUAL(src[i], d[i]);
}
diff --git a/test/test_pcm_main.cxx b/test/test_pcm_main.cxx
index c034181d9..0e397a15c 100644
--- a/test/test_pcm_main.cxx
+++ b/test/test_pcm_main.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,14 @@
#include <stdlib.h>
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmDitherTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmPackTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmChannelsTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmVolumeTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmFormatTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmMixTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(PcmExportTest);
+
int
main(gcc_unused int argc, gcc_unused char **argv)
{
diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx
index 2a8a11388..973b58f4d 100644
--- a/test/test_pcm_mix.cxx
+++ b/test/test_pcm_mix.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,32 +21,36 @@
#include "test_pcm_all.hxx"
#include "test_pcm_util.hxx"
#include "pcm/PcmMix.hxx"
+#include "pcm/PcmDither.hxx"
template<typename T, SampleFormat format, typename G=RandomInt<T>>
static void
TestPcmMix(G g=G())
{
- constexpr unsigned N = 256;
+ constexpr unsigned N = 509;
const auto src1 = TestDataBuffer<T, N>(g);
const auto src2 = TestDataBuffer<T, N>(g);
+ PcmDither dither;
+
/* portion1=1.0: result must be equal to src1 */
auto result = src1;
- bool success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ bool success = pcm_mix(dither,
+ result.begin(), src2.begin(), sizeof(result),
format, 1.0);
CPPUNIT_ASSERT(success);
- AssertEqualWithTolerance(result, src1, 1);
+ AssertEqualWithTolerance(result, src1, 3);
/* portion1=0.0: result must be equal to src2 */
result = src1;
- success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ success = pcm_mix(dither, result.begin(), src2.begin(), sizeof(result),
format, 0.0);
CPPUNIT_ASSERT(success);
- AssertEqualWithTolerance(result, src2, 1);
+ AssertEqualWithTolerance(result, src2, 3);
/* portion1=0.5 */
result = src1;
- success = pcm_mix(result.begin(), src2.begin(), sizeof(result),
+ success = pcm_mix(dither, result.begin(), src2.begin(), sizeof(result),
format, 0.5);
CPPUNIT_ASSERT(success);
@@ -54,7 +58,7 @@ TestPcmMix(G g=G())
for (unsigned i = 0; i < N; ++i)
expected[i] = (int64_t(src1[i]) + int64_t(src2[i])) / 2;
- AssertEqualWithTolerance(result, expected, 1);
+ AssertEqualWithTolerance(result, expected, 3);
}
void
diff --git a/test/test_pcm_pack.cxx b/test/test_pcm_pack.cxx
index cab78c499..fff3e10f0 100644
--- a/test/test_pcm_pack.cxx
+++ b/test/test_pcm_pack.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,7 +25,7 @@
void
PcmPackTest::TestPack24()
{
- constexpr unsigned N = 256;
+ constexpr unsigned N = 509;
const auto src = TestDataBuffer<int32_t, N>(RandomInt24());
uint8_t dest[N * 3];
@@ -49,7 +49,7 @@ PcmPackTest::TestPack24()
void
PcmPackTest::TestUnpack24()
{
- constexpr unsigned N = 256;
+ constexpr unsigned N = 509;
const auto src = TestDataBuffer<uint8_t, N * 3>();
int32_t dest[N];
diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx
index b378c75a7..f1efbc666 100644
--- a/test/test_pcm_util.hxx
+++ b/test/test_pcm_util.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "util/ConstBuffer.hxx"
+
#include <array>
#include <random>
@@ -76,6 +78,14 @@ public:
operator typename std::array<T, N>::const_pointer() const {
return begin();
}
+
+ operator ConstBuffer<T>() const {
+ return { begin(), size() };
+ }
+
+ operator ConstBuffer<void>() const {
+ return { begin(), size() * sizeof(T) };
+ }
};
template<typename T>
diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx
index 764d8b127..2d908f6c1 100644
--- a/test/test_pcm_volume.cxx
+++ b/test/test_pcm_volume.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,169 +17,109 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "test_pcm_all.hxx"
-#include "pcm/PcmVolume.hxx"
+#include "pcm/Volume.hxx"
+#include "pcm/Traits.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
#include "test_pcm_util.hxx"
#include <algorithm>
#include <string.h>
-void
-PcmVolumeTest::TestVolume8()
+template<SampleFormat F, class Traits=SampleTraits<F>,
+ typename G=RandomInt<typename Traits::value_type>>
+static void
+TestVolume(G g=G())
{
- constexpr unsigned N = 256;
- static int8_t zero[N];
- const auto src = TestDataBuffer<int8_t, N>();
+ typedef typename Traits::value_type value_type;
+
+ PcmVolume pv;
+ CPPUNIT_ASSERT(pv.Open(F, IgnoreError()));
- int8_t dest[N];
+ constexpr size_t N = 509;
+ static value_type zero[N];
+ const auto _src = TestDataBuffer<value_type, N>(g);
+ const ConstBuffer<void> src(_src, sizeof(_src));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S8, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
+ pv.SetVolume(0);
+ auto dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero)));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S8, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
+ pv.SetVolume(PCM_VOLUME_1);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S8, PCM_VOLUME_1 / 2));
+ pv.SetVolume(PCM_VOLUME_1 / 2);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ const auto _dest = ConstBuffer<value_type>::FromVoid(dest);
for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
+ const auto expected = (_src[i] + 1) / 2;
+ CPPUNIT_ASSERT(_dest[i] >= expected - 4);
+ CPPUNIT_ASSERT(_dest[i] <= expected + 4);
}
+
+ pv.Close();
}
void
-PcmVolumeTest::TestVolume16()
+PcmVolumeTest::TestVolume8()
{
- constexpr unsigned N = 256;
- static int16_t zero[N];
- const auto src = TestDataBuffer<int16_t, N>();
-
- int16_t dest[N];
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S16, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S16, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S16, PCM_VOLUME_1 / 2));
+ TestVolume<SampleFormat::S8>();
+}
- for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
- }
+void
+PcmVolumeTest::TestVolume16()
+{
+ TestVolume<SampleFormat::S16>();
}
void
PcmVolumeTest::TestVolume24()
{
- constexpr unsigned N = 256;
- static int32_t zero[N];
- const auto src = TestDataBuffer<int32_t, N>(RandomInt24());
-
- int32_t dest[N];
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S24_P32, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S24_P32, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S24_P32, PCM_VOLUME_1 / 2));
-
- for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
- }
+ TestVolume<SampleFormat::S24_P32>(RandomInt24());
}
void
PcmVolumeTest::TestVolume32()
{
- constexpr unsigned N = 256;
- static int32_t zero[N];
- const auto src = TestDataBuffer<int32_t, N>();
-
- int32_t dest[N];
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S32, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S32, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
-
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::S32, PCM_VOLUME_1 / 2));
-
- for (unsigned i = 0; i < N; ++i) {
- CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2);
- CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1);
- }
+ TestVolume<SampleFormat::S32>();
}
void
PcmVolumeTest::TestVolumeFloat()
{
- constexpr unsigned N = 256;
- static float zero[N];
- const auto src = TestDataBuffer<float, N>(RandomFloat());
+ PcmVolume pv;
+ CPPUNIT_ASSERT(pv.Open(SampleFormat::FLOAT, IgnoreError()));
- float dest[N];
+ constexpr size_t N = 509;
+ static float zero[N];
+ const auto _src = TestDataBuffer<float, N>(RandomFloat());
+ const ConstBuffer<void> src(_src, sizeof(_src));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::FLOAT, 0));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero)));
+ pv.SetVolume(0);
+ auto dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero)));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::FLOAT, PCM_VOLUME_1));
- CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src)));
+ pv.SetVolume(PCM_VOLUME_1);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size));
- std::copy(src.begin(), src.end(), dest);
- CPPUNIT_ASSERT_EQUAL(true,
- pcm_volume(dest, sizeof(dest),
- SampleFormat::FLOAT,
- PCM_VOLUME_1 / 2));
+ pv.SetVolume(PCM_VOLUME_1 / 2);
+ dest = pv.Apply(src);
+ CPPUNIT_ASSERT_EQUAL(src.size, dest.size);
+ const auto _dest = ConstBuffer<float>::FromVoid(dest);
for (unsigned i = 0; i < N; ++i)
- CPPUNIT_ASSERT_DOUBLES_EQUAL(src[i] / 2, dest[i], 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(_src[i] / 2, _dest[i], 1);
+
+ pv.Close();
}
diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx
index fca18fc2d..517cb028c 100644
--- a/test/test_queue_priority.cxx
+++ b/test/test_queue_priority.cxx
@@ -1,7 +1,6 @@
#include "config.h"
-#include "Queue.hxx"
-#include "Song.hxx"
-#include "Directory.hxx"
+#include "queue/Queue.hxx"
+#include "DetachedSong.hxx"
#include "util/Macros.hxx"
#include <cppunit/TestFixture.h>
@@ -9,21 +8,8 @@
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
-Directory detached_root;
-
-Directory::Directory() {}
-Directory::~Directory() {}
-
-Song *
-Song::DupDetached() const
-{
- return const_cast<Song *>(this);
-}
-
-void
-Song::Free()
-{
-}
+Tag::Tag(const Tag &) {}
+void Tag::Clear() {}
static void
check_descending_priority(const Queue *queue,
@@ -53,12 +39,29 @@ public:
void
QueuePriorityTest::TestPriority()
{
- static Song songs[16];
+ DetachedSong songs[16] = {
+ DetachedSong("0.ogg"),
+ DetachedSong("1.ogg"),
+ DetachedSong("2.ogg"),
+ DetachedSong("3.ogg"),
+ DetachedSong("4.ogg"),
+ DetachedSong("5.ogg"),
+ DetachedSong("6.ogg"),
+ DetachedSong("7.ogg"),
+ DetachedSong("8.ogg"),
+ DetachedSong("9.ogg"),
+ DetachedSong("a.ogg"),
+ DetachedSong("b.ogg"),
+ DetachedSong("c.ogg"),
+ DetachedSong("d.ogg"),
+ DetachedSong("e.ogg"),
+ DetachedSong("f.ogg"),
+ };
Queue queue(32);
for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i)
- queue.Append(&songs[i], 0);
+ queue.Append(DetachedSong(songs[i]), 0);
CPPUNIT_ASSERT_EQUAL(unsigned(ARRAY_SIZE(songs)), queue.GetLength());
diff --git a/test/test_rewind.cxx b/test/test_rewind.cxx
new file mode 100644
index 000000000..3ab37427a
--- /dev/null
+++ b/test/test_rewind.cxx
@@ -0,0 +1,154 @@
+/*
+ * Unit tests for class RewindInputStream.
+ */
+
+#include "config.h"
+#include "input/plugins/RewindInputPlugin.hxx"
+#include "input/InputStream.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "util/Error.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string>
+
+#include <string.h>
+#include <stdlib.h>
+
+class StringInputStream final : public InputStream {
+ const char *data;
+ size_t remaining;
+
+public:
+ StringInputStream(const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ const char *_data)
+ :InputStream(_uri, _mutex, _cond),
+ data(_data), remaining(strlen(data)) {
+ SetReady();
+ }
+
+ /* virtual methods from InputStream */
+ bool IsEOF() override {
+ return remaining == 0;
+ }
+
+ size_t Read(void *ptr, size_t read_size,
+ gcc_unused Error &error) override {
+ size_t nbytes = std::min(remaining, read_size);
+ memcpy(ptr, data, nbytes);
+ data += nbytes;
+ remaining -= nbytes;
+ offset += nbytes;
+ return nbytes;
+ }
+};
+
+class RewindTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(RewindTest);
+ CPPUNIT_TEST(TestRewind);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void TestRewind() {
+ Mutex mutex;
+ Cond cond;
+
+ StringInputStream *sis =
+ new StringInputStream("foo://", mutex, cond,
+ "foo bar");
+ CPPUNIT_ASSERT(sis->IsReady());
+
+ InputStream *ris = input_rewind_open(sis);
+ CPPUNIT_ASSERT(ris != sis);
+ CPPUNIT_ASSERT(ris != nullptr);
+
+ const ScopeLock protect(mutex);
+
+ ris->Update();
+ CPPUNIT_ASSERT(ris->IsReady());
+ CPPUNIT_ASSERT(!ris->KnownSize());
+ CPPUNIT_ASSERT_EQUAL(offset_type(0), ris->GetOffset());
+
+ Error error;
+ char buffer[16];
+ size_t nbytes = ris->Read(buffer, 2, error);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), nbytes);
+ CPPUNIT_ASSERT_EQUAL('f', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL('o', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(2), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ nbytes = ris->Read(buffer, 2, error);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), nbytes);
+ CPPUNIT_ASSERT_EQUAL('o', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL(' ', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(4), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ CPPUNIT_ASSERT(ris->Seek(1, error));
+ CPPUNIT_ASSERT_EQUAL(offset_type(1), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ nbytes = ris->Read(buffer, 2, error);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), nbytes);
+ CPPUNIT_ASSERT_EQUAL('o', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL('o', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(3), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ CPPUNIT_ASSERT(ris->Seek(0, error));
+ CPPUNIT_ASSERT_EQUAL(offset_type(0), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ nbytes = ris->Read(buffer, 2, error);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), nbytes);
+ CPPUNIT_ASSERT_EQUAL('f', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL('o', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(2), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ nbytes = ris->Read(buffer, sizeof(buffer), error);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), nbytes);
+ CPPUNIT_ASSERT_EQUAL('o', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL(' ', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(4), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ nbytes = ris->Read(buffer, sizeof(buffer), error);
+ CPPUNIT_ASSERT_EQUAL(size_t(3), nbytes);
+ CPPUNIT_ASSERT_EQUAL('b', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL('a', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL('r', buffer[2]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(7), ris->GetOffset());
+ CPPUNIT_ASSERT(ris->IsEOF());
+
+ CPPUNIT_ASSERT(ris->Seek(3, error));
+ CPPUNIT_ASSERT_EQUAL(offset_type(3), ris->GetOffset());
+ CPPUNIT_ASSERT(!ris->IsEOF());
+
+ nbytes = ris->Read(buffer, sizeof(buffer), error);
+ CPPUNIT_ASSERT_EQUAL(size_t(4), nbytes);
+ CPPUNIT_ASSERT_EQUAL(' ', buffer[0]);
+ CPPUNIT_ASSERT_EQUAL('b', buffer[1]);
+ CPPUNIT_ASSERT_EQUAL('a', buffer[2]);
+ CPPUNIT_ASSERT_EQUAL('r', buffer[3]);
+ CPPUNIT_ASSERT_EQUAL(offset_type(7), ris->GetOffset());
+ CPPUNIT_ASSERT(ris->IsEOF());
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RewindTest);
+
+int
+main(gcc_unused int argc, gcc_unused char **argv)
+{
+ CppUnit::TextUi::TestRunner runner;
+ auto &registry = CppUnit::TestFactoryRegistry::getRegistry();
+ runner.addTest(registry.makeTest());
+ return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/test_translate_song.cxx b/test/test_translate_song.cxx
new file mode 100644
index 000000000..e3c1bcb79
--- /dev/null
+++ b/test/test_translate_song.cxx
@@ -0,0 +1,318 @@
+/*
+ * Unit tests for playlist_check_translate_song().
+ */
+
+#include "config.h"
+#include "playlist/PlaylistSong.hxx"
+#include "DetachedSong.hxx"
+#include "SongLoader.hxx"
+#include "client/Client.hxx"
+#include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
+#include "util/Domain.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "ls.hxx"
+#include "Log.hxx"
+#include "db/DatabaseSong.hxx"
+#include "storage/plugins/LocalStorage.hxx"
+#include "util/Error.hxx"
+#include "Mapper.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string.h>
+#include <stdio.h>
+
+void
+Log(const Domain &domain, gcc_unused LogLevel level, const char *msg)
+{
+ fprintf(stderr, "[%s] %s\n", domain.GetName(), msg);
+}
+
+bool
+uri_supported_scheme(const char *uri)
+{
+ return memcmp(uri, "http://", 7) == 0;
+}
+
+static const char *const music_directory = "/music";
+static Storage *storage;
+
+static void
+BuildTag(gcc_unused TagBuilder &tag)
+{
+}
+
+template<typename... Args>
+static void
+BuildTag(TagBuilder &tag, TagType type, const char *value, Args&&... args)
+{
+ tag.AddItem(type, value);
+ BuildTag(tag, std::forward<Args>(args)...);
+}
+
+template<typename... Args>
+static Tag
+MakeTag(Args&&... args)
+{
+ TagBuilder tag;
+ BuildTag(tag, std::forward<Args>(args)...);
+ return tag.Commit();
+}
+
+static Tag
+MakeTag1a()
+{
+ return MakeTag(TAG_ARTIST, "artist_a1", TAG_TITLE, "title_a1",
+ TAG_ALBUM, "album_a1");
+}
+
+static Tag
+MakeTag1b()
+{
+ return MakeTag(TAG_ARTIST, "artist_b1", TAG_TITLE, "title_b1",
+ TAG_COMMENT, "comment_b1");
+}
+
+static Tag
+MakeTag1c()
+{
+ return MakeTag(TAG_ARTIST, "artist_b1", TAG_TITLE, "title_b1",
+ TAG_COMMENT, "comment_b1", TAG_ALBUM, "album_a1");
+}
+
+static Tag
+MakeTag2a()
+{
+ return MakeTag(TAG_ARTIST, "artist_a2", TAG_TITLE, "title_a2",
+ TAG_ALBUM, "album_a2");
+}
+
+static Tag
+MakeTag2b()
+{
+ return MakeTag(TAG_ARTIST, "artist_b2", TAG_TITLE, "title_b2",
+ TAG_COMMENT, "comment_b2");
+}
+
+static Tag
+MakeTag2c()
+{
+ return MakeTag(TAG_ARTIST, "artist_b2", TAG_TITLE, "title_b2",
+ TAG_COMMENT, "comment_b2", TAG_ALBUM, "album_a2");
+}
+
+static const char *uri1 = "/foo/bar.ogg";
+static const char *uri2 = "foo/bar.ogg";
+
+DetachedSong *
+DatabaseDetachSong(gcc_unused const Database &db,
+ gcc_unused const Storage &_storage,
+ const char *uri,
+ gcc_unused Error &error)
+{
+ if (strcmp(uri, uri2) == 0)
+ return new DetachedSong(uri, MakeTag2a());
+
+ return nullptr;
+}
+
+bool
+DetachedSong::Update()
+{
+ if (strcmp(GetURI(), uri1) == 0) {
+ SetTag(MakeTag1a());
+ return true;
+ }
+
+ return false;
+}
+
+const Database *
+Client::GetDatabase(gcc_unused Error &error) const
+{
+ return reinterpret_cast<const Database *>(this);
+}
+
+const Storage *
+Client::GetStorage() const
+{
+ return ::storage;
+}
+
+bool
+Client::AllowFile(gcc_unused Path path_fs, gcc_unused Error &error) const
+{
+ /* always return false, so a SongLoader with a non-nullptr
+ Client pointer will be regarded "insecure", while one with
+ client==nullptr will allow all files */
+ return false;
+}
+
+static std::string
+ToString(const Tag &tag)
+{
+ std::string result;
+
+ if (!tag.duration.IsNegative()) {
+ char buffer[64];
+ sprintf(buffer, "%d", tag.duration.ToMS());
+ result.append(buffer);
+ }
+
+ for (const auto &item : tag) {
+ result.push_back('|');
+ result.append(tag_item_names[item.type]);
+ result.push_back('=');
+ result.append(item.value);
+ }
+
+ return result;
+}
+
+static std::string
+ToString(const DetachedSong &song)
+{
+ std::string result = song.GetURI();
+ result.push_back('|');
+
+ char buffer[64];
+
+ if (song.GetLastModified() > 0) {
+ sprintf(buffer, "%lu", (unsigned long)song.GetLastModified());
+ result.append(buffer);
+ }
+
+ result.push_back('|');
+
+ if (song.GetStartTime().IsPositive()) {
+ sprintf(buffer, "%u", song.GetStartTime().ToMS());
+ result.append(buffer);
+ }
+
+ result.push_back('-');
+
+ if (song.GetEndTime().IsPositive()) {
+ sprintf(buffer, "%u", song.GetEndTime().ToMS());
+ result.append(buffer);
+ }
+
+ result.push_back('|');
+
+ result.append(ToString(song.GetTag()));
+
+ return result;
+}
+
+class TranslateSongTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TranslateSongTest);
+ CPPUNIT_TEST(TestAbsoluteURI);
+ CPPUNIT_TEST(TestInsecure);
+ CPPUNIT_TEST(TestSecure);
+ CPPUNIT_TEST(TestInDatabase);
+ CPPUNIT_TEST(TestRelative);
+ CPPUNIT_TEST_SUITE_END();
+
+ void TestAbsoluteURI() {
+ DetachedSong song1("http://example.com/foo.ogg");
+ auto se = ToString(song1);
+ const SongLoader loader(nullptr, nullptr);
+ CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/ignored",
+ loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song1));
+ }
+
+ void TestInsecure() {
+ /* illegal because secure=false */
+ DetachedSong song1 (uri1);
+ const SongLoader loader(*reinterpret_cast<const Client *>(1));
+ CPPUNIT_ASSERT(!playlist_check_translate_song(song1, nullptr,
+ loader));
+ }
+
+ void TestSecure() {
+ DetachedSong song1(uri1, MakeTag1b());
+ auto s1 = ToString(song1);
+ auto se = ToString(DetachedSong(uri1, MakeTag1c()));
+
+ const SongLoader loader(nullptr, nullptr);
+ CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/ignored",
+ loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song1));
+ }
+
+ void TestInDatabase() {
+ const SongLoader loader(reinterpret_cast<const Database *>(1),
+ storage);
+
+ DetachedSong song1("doesntexist");
+ CPPUNIT_ASSERT(!playlist_check_translate_song(song1, nullptr,
+ loader));
+
+ DetachedSong song2(uri2, MakeTag2b());
+ auto s1 = ToString(song2);
+ auto se = ToString(DetachedSong(uri2, MakeTag2c()));
+ CPPUNIT_ASSERT(playlist_check_translate_song(song2, nullptr,
+ loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song2));
+
+ DetachedSong song3("/music/foo/bar.ogg", MakeTag2b());
+ s1 = ToString(song3);
+ se = ToString(DetachedSong(uri2, MakeTag2c()));
+ CPPUNIT_ASSERT(playlist_check_translate_song(song3, nullptr,
+ loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song3));
+ }
+
+ void TestRelative() {
+ const Database &db = *reinterpret_cast<const Database *>(1);
+ const SongLoader secure_loader(&db, storage);
+ const SongLoader insecure_loader(*reinterpret_cast<const Client *>(1),
+ &db, storage);
+
+ /* map to music_directory */
+ DetachedSong song1("bar.ogg", MakeTag2b());
+ auto s1 = ToString(song1);
+ auto se = ToString(DetachedSong(uri2, MakeTag2c()));
+ CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/music/foo",
+ insecure_loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song1));
+
+ /* illegal because secure=false */
+ DetachedSong song2("bar.ogg", MakeTag2b());
+ CPPUNIT_ASSERT(!playlist_check_translate_song(song1, "/foo",
+ insecure_loader));
+
+ /* legal because secure=true */
+ DetachedSong song3("bar.ogg", MakeTag1b());
+ s1 = ToString(song3);
+ se = ToString(DetachedSong(uri1, MakeTag1c()));
+ CPPUNIT_ASSERT(playlist_check_translate_song(song3, "/foo",
+ secure_loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song3));
+
+ /* relative to http:// */
+ DetachedSong song4("bar.ogg", MakeTag2a());
+ s1 = ToString(song4);
+ se = ToString(DetachedSong("http://example.com/foo/bar.ogg", MakeTag2a()));
+ CPPUNIT_ASSERT(playlist_check_translate_song(song4, "http://example.com/foo",
+ insecure_loader));
+ CPPUNIT_ASSERT_EQUAL(se, ToString(song4));
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TranslateSongTest);
+
+int
+main(gcc_unused int argc, gcc_unused char **argv)
+{
+ storage = CreateLocalStorage(Path::FromFS(music_directory));
+
+ CppUnit::TextUi::TestRunner runner;
+ auto &registry = CppUnit::TestFactoryRegistry::getRegistry();
+ runner.addTest(registry.makeTest());
+ return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/test_util.cxx b/test/test_util.cxx
index 91e87957f..3e79aeca0 100644
--- a/test/test_util.cxx
+++ b/test/test_util.cxx
@@ -4,6 +4,7 @@
#include "config.h"
#include "util/UriUtil.hxx"
+#include "TestCircularBuffer.hxx"
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
@@ -63,10 +64,13 @@ public:
uri_remove_auth("http://foo@www.example.com/"));
CPPUNIT_ASSERT_EQUAL(std::string(),
uri_remove_auth("http://www.example.com/f:oo@bar"));
+ CPPUNIT_ASSERT_EQUAL(std::string("ftp://ftp.example.com/"),
+ uri_remove_auth("ftp://foo:bar@ftp.example.com/"));
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(UriUtilTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(TestCircularBuffer);
int
main(gcc_unused int argc, gcc_unused char **argv)
diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx
index 1d95f6deb..59b901da2 100644
--- a/test/test_vorbis_encoder.cxx
+++ b/test/test_vorbis_encoder.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -18,12 +18,13 @@
*/
#include "config.h"
-#include "EncoderList.hxx"
-#include "EncoderPlugin.hxx"
+#include "encoder/EncoderList.hxx"
+#include "encoder/EncoderPlugin.hxx"
#include "AudioFormat.hxx"
-#include "ConfigData.hxx"
+#include "config/ConfigData.hxx"
#include "stdbin.h"
#include "tag/Tag.hxx"
+#include "tag/TagBuilder.hxx"
#include "util/Error.hxx"
#include <stddef.h>
@@ -81,8 +82,13 @@ main(gcc_unused int argc, gcc_unused char **argv)
encoder_to_stdout(*encoder);
Tag tag;
- tag.AddItem(TAG_ARTIST, "Foo");
- tag.AddItem(TAG_TITLE, "Bar");
+
+ {
+ TagBuilder tag_builder;
+ tag_builder.AddItem(TAG_ARTIST, "Foo");
+ tag_builder.AddItem(TAG_TITLE, "Bar");
+ tag_builder.Commit(tag);
+ }
success = encoder_tag(encoder, &tag, IgnoreError());
assert(success);
diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx
index 6e66c4696..1ff3ba484 100644
--- a/test/visit_archive.cxx
+++ b/test/visit_archive.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,31 +20,23 @@
#include "config.h"
#include "stdbin.h"
#include "tag/Tag.hxx"
-#include "ConfigGlobal.hxx"
-#include "IOThread.hxx"
-#include "InputInit.hxx"
-#include "ArchiveList.hxx"
-#include "ArchivePlugin.hxx"
-#include "ArchiveFile.hxx"
-#include "ArchiveVisitor.hxx"
+#include "config/ConfigGlobal.hxx"
+#include "ScopeIOThread.hxx"
+#include "input/Init.hxx"
+#include "archive/ArchiveList.hxx"
+#include "archive/ArchivePlugin.hxx"
+#include "archive/ArchiveFile.hxx"
+#include "archive/ArchiveVisitor.hxx"
#include "fs/Path.hxx"
#include "util/Error.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <unistd.h>
#include <stdlib.h>
-static void
-my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
- const gchar *message, gcc_unused gpointer user_data)
-{
- if (log_domain != NULL)
- g_printerr("%s: %s\n", log_domain, message);
- else
- g_printerr("%s\n", message);
-}
-
class MyArchiveVisitor final : public ArchiveVisitor {
public:
virtual void VisitArchiveEntry(const char *path_utf8) override {
@@ -67,18 +59,17 @@ main(int argc, char **argv)
/* initialize GLib */
+#ifdef HAVE_GLIB
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(NULL);
#endif
-
- g_log_set_default_handler(my_log_func, NULL);
+#endif
/* initialize MPD */
config_global_init();
- io_thread_init();
- io_thread_start();
+ const ScopeIOThread io_thread;
archive_plugin_init_all();
@@ -89,7 +80,7 @@ main(int argc, char **argv)
/* open the archive and dump it */
- const archive_plugin *plugin = archive_plugin_from_name(plugin_name);
+ const ArchivePlugin *plugin = archive_plugin_from_name(plugin_name);
if (plugin == nullptr) {
fprintf(stderr, "No such plugin: %s\n", plugin_name);
return EXIT_FAILURE;
@@ -97,7 +88,7 @@ main(int argc, char **argv)
int result = EXIT_SUCCESS;
- ArchiveFile *file = archive_file_open(plugin, path.c_str(), error);
+ ArchiveFile *file = archive_file_open(plugin, path, error);
if (file != nullptr) {
MyArchiveVisitor visitor;
file->Visit(visitor);
@@ -113,8 +104,6 @@ main(int argc, char **argv)
archive_plugin_deinit_all();
- io_thread_deinit();
-
config_global_finish();
return result;
diff --git a/valgrind.suppressions b/valgrind.suppressions
index 03f2025ee..d35e8d7e8 100644
--- a/valgrind.suppressions
+++ b/valgrind.suppressions
@@ -297,34 +297,6 @@
fun:dlclose
}
-# is that a leak in libdbus?
-
-{
- <insert a suppression name here>
- Memcheck:Leak
- fun:?alloc
- ...
- obj:*/libdbus-*.so.*
- fun:avahi_client_new
-}
-
-{
- <insert a suppression name here>
- Memcheck:Leak
- fun:malloc
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_message_new_error
- obj:/usr/lib/libdbus-1.so.3.4.0
- fun:dbus_connection_send_with_reply
- fun:dbus_connection_send_with_reply_and_block
- obj:/usr/lib/libavahi-client.so.3.2.4
- fun:avahi_entry_group_new
- fun:avahiRegisterService
- fun:avahiClientCallback
- obj:/usr/lib/libavahi-client.so.3.2.4
- fun:avahi_client_new
-}
-
{
inet_ntoa
Memcheck:Leak
@@ -485,3 +457,100 @@
fun:call_init
fun:_dl_init
}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Cond
+ fun:index
+ fun:expand_dynamic_string_token
+ fun:fillin_rpath
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Cond
+ fun:index
+ fun:expand_dynamic_string_token
+ ...
+ fun:do_preload
+}
+
+#
+# libopenal
+#
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:calloc
+ obj:*/libopenal.so*
+ ...
+ fun:_dl_init
+}
+
+#
+# libadplug
+#
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:malloc
+ ...
+ obj:*/libadplug*.so*
+ ...
+ fun:_dl_init
+}
+
+#
+# libjack
+#
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:_Znwm
+ obj:*/libjack.so*
+ ...
+ fun:_dl_init
+}
+
+#
+# libsmbclient
+#
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:smbc_*_context
+ fun:smbc_init
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:smbc_setDebug
+ fun:smbc_init
+}
+
+#
+# libgomp
+#
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:malloc
+ ...
+ fun:gomp_init_num_threads
+ ...
+ fun:_dl_init
+}