aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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/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.cxx321
-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.cxx4
-rw-r--r--src/CrossFade.hxx2
-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.cxx159
-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.cxx581
-rw-r--r--src/DecoderAPI.hxx209
-rw-r--r--src/DecoderBuffer.cxx167
-rw-r--r--src/DecoderBuffer.hxx105
-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.cxx100
-rw-r--r--src/DecoderInternal.hxx117
-rw-r--r--src/DecoderList.cxx237
-rw-r--r--src/DecoderList.hxx92
-rw-r--r--src/DecoderPlugin.cxx42
-rw-r--r--src/DecoderPlugin.hxx181
-rw-r--r--src/DecoderPrint.cxx55
-rw-r--r--src/DecoderPrint.hxx28
-rw-r--r--src/DecoderThread.cxx450
-rw-r--r--src/DecoderThread.hxx28
-rw-r--r--src/DespotifyUtils.cxx154
-rw-r--r--src/DespotifyUtils.hxx71
-rw-r--r--src/DetachedSong.cxx67
-rw-r--r--src/DetachedSong.hxx221
-rw-r--r--src/Directory.cxx331
-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.cxx192
-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.cxx228
-rw-r--r--src/LogBackend.hxx45
-rw-r--r--src/LogInit.cxx254
-rw-r--r--src/LogInit.hxx2
-rw-r--r--src/LogLevel.hxx59
-rw-r--r--src/LogV.hxx4
-rw-r--r--src/Main.cxx408
-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.cxx2
-rw-r--r--src/MusicBuffer.hxx2
-rw-r--r--src/MusicChunk.cxx2
-rw-r--r--src/MusicChunk.hxx2
-rw-r--r--src/MusicPipe.cxx2
-rw-r--r--src/MusicPipe.hxx2
-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.cxx679
-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.hxx49
-rw-r--r--src/Permission.cxx12
-rw-r--r--src/Permission.hxx2
-rw-r--r--src/PlayerControl.cxx44
-rw-r--r--src/PlayerControl.hxx50
-rw-r--r--src/PlayerListener.hxx36
-rw-r--r--src/PlayerThread.cxx146
-rw-r--r--src/PlayerThread.hxx2
-rw-r--r--src/Playlist.cxx341
-rw-r--r--src/Playlist.hxx263
-rw-r--r--src/PlaylistAny.cxx70
-rw-r--r--src/PlaylistAny.hxx41
-rw-r--r--src/PlaylistControl.cxx261
-rw-r--r--src/PlaylistDatabase.cxx6
-rw-r--r--src/PlaylistDatabase.hxx2
-rw-r--r--src/PlaylistEdit.cxx428
-rw-r--r--src/PlaylistError.cxx2
-rw-r--r--src/PlaylistError.hxx2
-rw-r--r--src/PlaylistFile.cxx75
-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.cxx341
-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.cxx63
-rw-r--r--src/SongFilter.hxx18
-rw-r--r--src/SongLoader.cxx111
-rw-r--r--src/SongLoader.hxx77
-rw-r--r--src/SongPointer.hxx63
-rw-r--r--src/SongPrint.cxx88
-rw-r--r--src/SongPrint.hxx15
-rw-r--r--src/SongSave.cxx75
-rw-r--r--src/SongSave.hxx10
-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.cxx29
-rw-r--r--src/StateFile.hxx2
-rw-r--r--src/Stats.cxx99
-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.cxx103
-rw-r--r--src/TagFile.hxx5
-rw-r--r--src/TagPrint.cxx13
-rw-r--r--src/TagPrint.hxx9
-rw-r--r--src/TagSave.cxx5
-rw-r--r--src/TagSave.hxx2
-rw-r--r--src/TagStream.cxx78
-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/Win32Main.cxx173
-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.cxx225
-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.cxx192
-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.cxx76
-rw-r--r--src/client/ClientSubscribe.cxx87
-rw-r--r--src/client/ClientWrite.cxx61
-rw-r--r--src/command/AllCommands.cxx61
-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.cxx179
-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.cxx238
-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.cxx73
-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.cxx154
-rw-r--r--src/command/QueueCommands.hxx40
-rw-r--r--src/command/StickerCommands.cxx60
-rw-r--r--src/command/StickerCommands.hxx4
-rw-r--r--src/command/StorageCommands.cxx295
-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.hxx91
-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.cxx97
-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.cxx147
-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.cxx223
-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.cxx53
-rw-r--r--src/db/DatabaseSong.hxx50
-rw-r--r--src/db/Helpers.cxx100
-rw-r--r--src/db/Helpers.hxx32
-rw-r--r--src/db/Interface.hxx126
-rw-r--r--src/db/LightDirectory.hxx61
-rw-r--r--src/db/LightSong.cxx33
-rw-r--r--src/db/LightSong.hxx92
-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.hxx51
-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.cxx816
-rw-r--r--src/db/plugins/ProxyDatabasePlugin.hxx27
-rw-r--r--src/db/plugins/simple/DatabaseSave.cxx159
-rw-r--r--src/db/plugins/simple/DatabaseSave.hxx35
-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.cxx206
-rw-r--r--src/db/plugins/simple/DirectorySave.hxx35
-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.cxx495
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.hxx144
-rw-r--r--src/db/plugins/simple/Song.cxx111
-rw-r--r--src/db/plugins/simple/Song.hxx129
-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.cxx204
-rw-r--r--src/db/plugins/upnp/Directory.cxx262
-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.cxx786
-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.hxx70
-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.cxx114
-rw-r--r--src/db/update/InotifySource.hxx74
-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.hxx61
-rw-r--r--src/db/update/Service.cxx279
-rw-r--r--src/db/update/Service.hxx115
-rw-r--r--src/db/update/UpdateDomain.cxx23
-rw-r--r--src/db/update/UpdateDomain.hxx25
-rw-r--r--src/db/update/UpdateIO.cxx106
-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.hxx156
-rw-r--r--src/decoder/AdPlugDecoderPlugin.cxx141
-rw-r--r--src/decoder/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/AudiofileDecoderPlugin.cxx282
-rw-r--r--src/decoder/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/DecoderAPI.cxx629
-rw-r--r--src/decoder/DecoderAPI.hxx223
-rw-r--r--src/decoder/DecoderBuffer.cxx168
-rw-r--r--src/decoder/DecoderBuffer.hxx111
-rw-r--r--src/decoder/DecoderCommand.hxx32
-rw-r--r--src/decoder/DecoderControl.cxx140
-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.cxx101
-rw-r--r--src/decoder/DecoderInternal.hxx125
-rw-r--r--src/decoder/DecoderList.cxx161
-rw-r--r--src/decoder/DecoderList.hxx89
-rw-r--r--src/decoder/DecoderPlugin.cxx42
-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.cxx481
-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.cxx525
-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.cxx497
-rw-r--r--src/decoder/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/FfmpegDecoderPlugin.cxx709
-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.cxx274
-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.cxx142
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.h25
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.cxx288
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/DsdLib.cxx131
-rw-r--r--src/decoder/plugins/DsdLib.hxx78
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx522
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx358
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.cxx464
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx731
-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.cxx190
-rw-r--r--src/decoder/plugins/FlacCommon.hxx93
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.cxx384
-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.cxx223
-rw-r--r--src/decoder/plugins/FlacMetadata.hxx140
-rw-r--r--src/decoder/plugins/FlacPcm.cxx110
-rw-r--r--src/decoder/plugins/FlacPcm.hxx33
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx225
-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.cxx1155
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.cxx249
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.cxx215
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.cxx278
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.cxx256
-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.cxx69
-rw-r--r--src/decoder/plugins/OggFind.hxx56
-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.cxx482
-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.cxx435
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.cxx295
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/VorbisComments.cxx141
-rw-r--r--src/decoder/plugins/VorbisComments.hxx39
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.cxx380
-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.cxx573
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.hxx25
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.cxx159
-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.cxx365
-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.hxx5
-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.hxx55
-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.hxx35
-rw-r--r--src/event/Loop.cxx203
-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.cxx33
-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.cxx181
-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.hxx74
-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.cxx83
-rw-r--r--src/filter/NullFilterPlugin.cxx61
-rw-r--r--src/filter/ReplayGainFilterPlugin.cxx235
-rw-r--r--src/filter/ReplayGainFilterPlugin.hxx52
-rw-r--r--src/filter/RouteFilterPlugin.cxx297
-rw-r--r--src/filter/VolumeFilterPlugin.cxx143
-rw-r--r--src/filter/VolumeFilterPlugin.hxx31
-rw-r--r--src/filter/plugins/AutoConvertFilterPlugin.cxx131
-rw-r--r--src/filter/plugins/AutoConvertFilterPlugin.hxx34
-rw-r--r--src/filter/plugins/ChainFilterPlugin.cxx181
-rw-r--r--src/filter/plugins/ChainFilterPlugin.hxx48
-rw-r--r--src/filter/plugins/ConvertFilterPlugin.cxx149
-rw-r--r--src/filter/plugins/ConvertFilterPlugin.hxx37
-rw-r--r--src/filter/plugins/NormalizeFilterPlugin.cxx82
-rw-r--r--src/filter/plugins/NullFilterPlugin.cxx61
-rw-r--r--src/filter/plugins/ReplayGainFilterPlugin.cxx210
-rw-r--r--src/filter/plugins/ReplayGainFilterPlugin.hxx52
-rw-r--r--src/filter/plugins/RouteFilterPlugin.cxx296
-rw-r--r--src/filter/plugins/VolumeFilterPlugin.cxx106
-rw-r--r--src/filter/plugins/VolumeFilterPlugin.hxx31
-rw-r--r--src/fs/AllocatedPath.cxx27
-rw-r--r--src/fs/AllocatedPath.hxx56
-rw-r--r--src/fs/Charset.cxx50
-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.cxx315
-rw-r--r--src/fs/StandardDirectory.hxx71
-rw-r--r--src/fs/TextFile.cxx81
-rw-r--r--src/fs/TextFile.hxx62
-rw-r--r--src/fs/Traits.cxx136
-rw-r--r--src/fs/Traits.hxx168
-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.hxx128
-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/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.hxx67
-rw-r--r--src/input/Init.cxx87
-rw-r--r--src/input/Init.hxx36
-rw-r--r--src/input/InputPlugin.hxx87
-rw-r--r--src/input/InputStream.cxx125
-rw-r--r--src/input/InputStream.hxx364
-rw-r--r--src/input/MmsInputPlugin.cxx130
-rw-r--r--src/input/MmsInputPlugin.hxx25
-rw-r--r--src/input/Open.cxx71
-rw-r--r--src/input/ProxyInputStream.cxx97
-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.cxx78
-rw-r--r--src/input/TextInputStream.hxx56
-rw-r--r--src/input/ThreadInputStream.cxx165
-rw-r--r--src/input/ThreadInputStream.hxx144
-rw-r--r--src/input/plugins/AlsaInputPlugin.cxx383
-rw-r--r--src/input/plugins/AlsaInputPlugin.hxx28
-rw-r--r--src/input/plugins/ArchiveInputPlugin.cxx92
-rw-r--r--src/input/plugins/ArchiveInputPlugin.hxx25
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.cxx375
-rw-r--r--src/input/plugins/CdioParanoiaInputPlugin.hxx28
-rw-r--r--src/input/plugins/CurlInputPlugin.cxx840
-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.cxx155
-rw-r--r--src/input/plugins/FfmpegInputPlugin.hxx28
-rw-r--r--src/input/plugins/FileInputPlugin.cxx132
-rw-r--r--src/input/plugins/FileInputPlugin.hxx25
-rw-r--r--src/input/plugins/MmsInputPlugin.cxx110
-rw-r--r--src/input/plugins/MmsInputPlugin.hxx25
-rw-r--r--src/input/plugins/NfsInputPlugin.cxx221
-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/icu/Collate.cxx198
-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/Callback.hxx33
-rw-r--r--src/lib/nfs/Cancellable.hxx177
-rw-r--r--src/lib/nfs/Connection.cxx417
-rw-r--r--src/lib/nfs/Connection.hxx176
-rw-r--r--src/lib/nfs/Domain.cxx24
-rw-r--r--src/lib/nfs/Domain.hxx27
-rw-r--r--src/lib/nfs/FileReader.cxx244
-rw-r--r--src/lib/nfs/FileReader.hxx94
-rw-r--r--src/lib/nfs/Glue.cxx69
-rw-r--r--src/lib/nfs/Glue.hxx38
-rw-r--r--src/lib/nfs/Lease.hxx48
-rw-r--r--src/lib/nfs/Manager.cxx57
-rw-r--r--src/lib/nfs/Manager.hxx79
-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.cxx93
-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.cxx339
-rw-r--r--src/lib/upnp/Discovery.hxx163
-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.cxx166
-rw-r--r--src/lib/upnp/Util.hxx46
-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/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.cxx122
-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.cxx858
-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.cxx335
-rw-r--r--src/output/Internal.hxx436
-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.hxx275
-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.cxx776
-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.cxx87
-rw-r--r--src/output/OutputState.hxx46
-rw-r--r--src/output/OutputThread.cxx678
-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.cxx853
-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.cxx772
-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.cxx885
-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.cxx423
-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.hxx273
-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.hxx45
-rw-r--r--src/pcm/FloatConvert.hxx64
-rw-r--r--src/pcm/FormatConverter.cxx89
-rw-r--r--src/pcm/FormatConverter.hxx84
-rw-r--r--src/pcm/GlueResampler.cxx85
-rw-r--r--src/pcm/GlueResampler.hxx63
-rw-r--r--src/pcm/LibsamplerateResampler.cxx163
-rw-r--r--src/pcm/LibsamplerateResampler.hxx55
-rw-r--r--src/pcm/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.cxx334
-rw-r--r--src/pcm/PcmConvert.hxx67
-rw-r--r--src/pcm/PcmDither.cxx76
-rw-r--r--src/pcm/PcmDither.hxx46
-rw-r--r--src/pcm/PcmDsd.cxx24
-rw-r--r--src/pcm/PcmDsd.hxx12
-rw-r--r--src/pcm/PcmDsdUsb.cxx4
-rw-r--r--src/pcm/PcmDsdUsb.hxx2
-rw-r--r--src/pcm/PcmExport.cxx2
-rw-r--r--src/pcm/PcmExport.hxx2
-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.cxx162
-rw-r--r--src/pcm/SoxrResampler.hxx50
-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/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.cxx157
-rw-r--r--src/playlist/ExtM3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/M3uPlaylistPlugin.cxx83
-rw-r--r--src/playlist/M3uPlaylistPlugin.hxx25
-rw-r--r--src/playlist/MemorySongEnumerator.cxx32
-rw-r--r--src/playlist/MemorySongEnumerator.hxx38
-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.cxx282
-rw-r--r--src/playlist/PlaylistRegistry.hxx74
-rw-r--r--src/playlist/PlaylistSong.cxx97
-rw-r--r--src/playlist/PlaylistSong.hxx36
-rw-r--r--src/playlist/PlaylistStream.cxx93
-rw-r--r--src/playlist/PlaylistStream.hxx45
-rw-r--r--src/playlist/PlsPlaylistPlugin.cxx182
-rw-r--r--src/playlist/PlsPlaylistPlugin.hxx25
-rw-r--r--src/playlist/Print.cxx74
-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.cxx318
-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.cxx149
-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.cxx2
-rw-r--r--src/protocol/ArgParser.hxx2
-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.cxx333
-rw-r--r--src/queue/Playlist.hxx270
-rw-r--r--src/queue/PlaylistControl.cxx260
-rw-r--r--src/queue/PlaylistEdit.cxx398
-rw-r--r--src/queue/PlaylistState.cxx245
-rw-r--r--src/queue/PlaylistState.hxx54
-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.cxx119
-rw-r--r--src/queue/QueueSave.hxx44
-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.cxx360
-rw-r--r--src/storage/CompositeStorage.hxx166
-rw-r--r--src/storage/Configured.cxx81
-rw-r--r--src/storage/Configured.hxx44
-rw-r--r--src/storage/FileInfo.hxx62
-rw-r--r--src/storage/Registry.cxx71
-rw-r--r--src/storage/Registry.hxx44
-rw-r--r--src/storage/StorageInterface.cxx37
-rw-r--r--src/storage/StorageInterface.hxx81
-rw-r--r--src/storage/StoragePlugin.hxx34
-rw-r--r--src/storage/plugins/LocalStorage.cxx218
-rw-r--r--src/storage/plugins/LocalStorage.hxx36
-rw-r--r--src/storage/plugins/NfsStorage.cxx238
-rw-r--r--src/storage/plugins/NfsStorage.hxx29
-rw-r--r--src/storage/plugins/SmbclientStorage.cxx211
-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.cxx57
-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.cxx2
-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/Riff.cxx3
-rw-r--r--src/tag/Riff.hxx2
-rw-r--r--src/tag/Set.cxx116
-rw-r--r--src/tag/Set.hxx73
-rw-r--r--src/tag/Tag.cxx111
-rw-r--r--src/tag/Tag.hxx41
-rw-r--r--src/tag/TagBuilder.cxx162
-rw-r--r--src/tag/TagBuilder.hxx49
-rw-r--r--src/tag/TagConfig.cxx18
-rw-r--r--src/tag/TagConfig.hxx4
-rw-r--r--src/tag/TagHandler.cxx2
-rw-r--r--src/tag/TagHandler.hxx2
-rw-r--r--src/tag/TagId3.cxx175
-rw-r--r--src/tag/TagId3.hxx12
-rw-r--r--src/tag/TagItem.hxx2
-rw-r--r--src/tag/TagNames.c2
-rw-r--r--src/tag/TagPool.cxx90
-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.cxx33
-rw-r--r--src/tag/TagString.hxx2
-rw-r--r--src/tag/TagTable.cxx12
-rw-r--r--src/tag/TagTable.hxx11
-rw-r--r--src/tag/TagType.h2
-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.hxx9
-rw-r--r--src/thread/PosixMutex.hxx9
-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.hxx58
-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.hxx199
-rw-r--r--src/util/Error.cxx43
-rw-r--r--src/util/Error.hxx42
-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.hxx40
-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/StringUtil.cxx49
-rw-r--r--src/util/StringUtil.hxx31
-rw-r--r--src/util/Tokenizer.cxx41
-rw-r--r--src/util/Tokenizer.hxx40
-rw-r--r--src/util/UriUtil.cxx14
-rw-r--r--src/util/UriUtil.hxx9
-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/win/mpd_win32_rc.rc.in34
-rw-r--r--src/win32/Win32Main.cxx173
-rw-r--r--src/win32/mpd.ico (renamed from src/win/mpd.ico)bin353118 -> 353118 bytes
-rw-r--r--src/win32/mpd_win32_rc.rc.in34
-rw-r--r--src/zeroconf/AvahiPoll.cxx146
-rw-r--r--src/zeroconf/AvahiPoll.hxx48
-rw-r--r--src/zeroconf/ZeroconfAvahi.cxx279
-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
1376 files changed, 83222 insertions, 66089 deletions
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/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 f0bc6b0f7..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 382b76083..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 05f0a358c..cc278c0fd 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.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,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 const OptionDef opt_kill(
+ "kill", "kill the currently running mpd session");
+static const OptionDef opt_no_config(
+ "no-config", "don't read from config");
+static const OptionDef opt_no_daemon(
+ "no-daemon", "don't detach from console");
+static const OptionDef opt_stdout(
+ "stdout", nullptr); // hidden, compatibility with old versions
+static const OptionDef opt_stderr(
+ "stderr", "print messages to stderr");
+static const OptionDef opt_verbose(
+ "verbose", 'v', "verbose logging");
+static const OptionDef opt_version(
+ "version", 'V', "print version number");
+static const OptionDef opt_help(
+ "help", 'h', "show help options");
+static const OptionDef opt_help_alt(
+ nullptr, '?', nullptr); // hidden, standard alias for --help
+
static constexpr Domain cmdline_domain("cmdline");
gcc_noreturn
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-2013 Max Kellermann <max@duempel.org>\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 94abdcff3..44a87c7ba 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..bb585db5a 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
@@ -26,8 +26,6 @@
#include "Log.hxx"
#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
static constexpr Domain cross_fade_domain("cross_fade");
diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx
index c47db84e1..b385ea316 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
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 013a3e329..000000000
--- a/src/DatabaseGlue.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"
-#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 (db == nullptr)
- return nullptr;
-
- Directory *music_root = db_get_root();
- if (name == nullptr)
- return music_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 e4122d60e..000000000
--- a/src/DecoderAPI.cxx
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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(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;
-
- /* 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 6aad53cb2..000000000
--- a/src/DecoderBuffer.cxx
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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);
-}
-
-bool
-decoder_buffer_is_empty(const DecoderBuffer *buffer)
-{
- return buffer->consumed == buffer->length;
-}
-
-bool
-decoder_buffer_is_full(const DecoderBuffer *buffer)
-{
- return buffer->consumed == 0 && buffer->length == buffer->size;
-}
-
-static void
-decoder_buffer_shift(DecoderBuffer *buffer)
-{
- assert(buffer->consumed > 0);
-
- buffer->length -= buffer->consumed;
- memmove(buffer->data, buffer->data + buffer->consumed, buffer->length);
- buffer->consumed = 0;
-}
-
-bool
-decoder_buffer_fill(DecoderBuffer *buffer)
-{
- size_t nbytes;
-
- if (buffer->consumed > 0)
- decoder_buffer_shift(buffer);
-
- if (buffer->length >= buffer->size)
- /* buffer is full */
- return false;
-
- nbytes = decoder_read(buffer->decoder, *buffer->is,
- buffer->data + buffer->length,
- buffer->size - buffer->length);
- if (nbytes == 0)
- /* end of file, I/O error or decoder command
- received */
- return false;
-
- buffer->length += nbytes;
- assert(buffer->length <= buffer->size);
-
- return true;
-}
-
-const void *
-decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
-{
- if (buffer->consumed >= buffer->length)
- /* buffer is empty */
- return nullptr;
-
- *length_r = buffer->length - buffer->consumed;
- return buffer->data + buffer->consumed;
-}
-
-void
-decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes)
-{
- /* just move the "consumed" pointer - decoder_buffer_shift()
- will do the real work later (called by
- decoder_buffer_fill()) */
- buffer->consumed += nbytes;
-
- assert(buffer->consumed <= buffer->length);
-}
-
-bool
-decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes)
-{
- size_t length;
- const void *data;
- bool success;
-
- /* this could probably be optimized by seeking */
-
- while (true) {
- data = decoder_buffer_read(buffer, &length);
- if (data != nullptr) {
- if (length > nbytes)
- length = nbytes;
- decoder_buffer_consume(buffer, length);
- nbytes -= length;
- if (nbytes == 0)
- return true;
- }
-
- success = decoder_buffer_fill(buffer);
- if (!success)
- return false;
- }
-}
diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx
deleted file mode 100644
index 92cc31aa4..000000000
--- a/src/DecoderBuffer.hxx
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_DECODER_BUFFER_HXX
-#define MPD_DECODER_BUFFER_HXX
-
-#include <stddef.h>
-
-/**
- * This objects handles buffered reads in decoder plugins easily. You
- * create a buffer object, and use its high-level methods to fill and
- * read it. It will automatically handle shifting the buffer.
- */
-struct DecoderBuffer;
-
-struct Decoder;
-struct 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);
-
-bool
-decoder_buffer_is_empty(const DecoderBuffer *buffer);
-
-bool
-decoder_buffer_is_full(const DecoderBuffer *buffer);
-
-/**
- * Read data from the input_stream and append it to the buffer.
- *
- * @return true if data was appended; false if there is no data
- * available (yet), end of file, I/O error or a decoder command was
- * received
- */
-bool
-decoder_buffer_fill(DecoderBuffer *buffer);
-
-/**
- * Reads data from the buffer. This data is not yet consumed, you
- * have to call decoder_buffer_consume() to do that. The returned
- * buffer becomes invalid after a decoder_buffer_fill() or a
- * decoder_buffer_consume() call.
- *
- * @param buffer the decoder_buffer object
- * @param length_r pointer to a size_t where you will receive the
- * number of bytes available
- * @return a pointer to the read buffer, or 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 d5f40ad48..000000000
--- a/src/DecoderInternal.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 "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.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 834178260..000000000
--- a/src/DecoderList.cxx
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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
- &dsdiff_decoder_plugin,
- &dsf_decoder_plugin,
-#ifdef HAVE_FAAD
- &faad_decoder_plugin,
-#endif
-#ifdef HAVE_MPCDEC
- &mpcdec_decoder_plugin,
-#endif
-#ifdef HAVE_WAVPACK
- &wavpack_decoder_plugin,
-#endif
-#ifdef HAVE_MODPLUG
- &modplug_decoder_plugin,
-#endif
-#ifdef ENABLE_MIKMOD_DECODER
- &mikmod_decoder_plugin,
-#endif
-#ifdef ENABLE_SIDPLAY
- &sidplay_decoder_plugin,
-#endif
-#ifdef ENABLE_WILDMIDI
- &wildmidi_decoder_plugin,
-#endif
-#ifdef ENABLE_FLUIDSYNTH
- &fluidsynth_decoder_plugin,
-#endif
-#ifdef HAVE_ADPLUG
- &adplug_decoder_plugin,
-#endif
-#ifdef HAVE_FFMPEG
- &ffmpeg_decoder_plugin,
-#endif
-#ifdef HAVE_GME
- &gme_decoder_plugin,
-#endif
- &pcm_decoder_plugin,
- 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 77ed90882..000000000
--- a/src/DecoderPlugin.cxx
+++ /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.
- */
-
-#include "config.h"
-#include "DecoderPlugin.hxx"
-#include "util/StringUtil.hxx"
-
-#include <assert.h>
-
-bool
-DecoderPlugin::SupportsSuffix(const char *suffix) const
-{
- assert(suffix != nullptr);
-
- return suffixes != nullptr && string_array_contains(suffixes, suffix);
-
-}
-
-bool
-DecoderPlugin::SupportsMimeType(const char *mime_type) const
-{
- assert(mime_type != nullptr);
-
- 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 72fc3cfb4..000000000
--- a/src/DecoderThread.cxx
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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 "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)
-{
- const char *const suffix = uri_get_suffix(uri);
-
- 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;
-
- /* fall through */
-
- case DecoderCommand::SEEK:
- 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..eb377e591
--- /dev/null
+++ b/src/DetachedSong.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 "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_ms(other.start_ms), end_ms(other.end_ms) {}
+
+DetachedSong::~DetachedSong()
+{
+ /* this destructor exists here just so it won't get 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);
+}
+
+double
+DetachedSong::GetDuration() const
+{
+ if (end_ms > 0)
+ return (end_ms - start_ms) / 1000.0;
+
+ return tag.time - start_ms / 1000.0;
+}
diff --git a/src/DetachedSong.hxx b/src/DetachedSong.hxx
new file mode 100644
index 000000000..7ea0bc8d8
--- /dev/null
+++ b/src/DetachedSong.hxx
@@ -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.
+ */
+
+#ifndef MPD_DETACHED_SONG_HXX
+#define MPD_DETACHED_SONG_HXX
+
+#include "check.h"
+#include "tag/Tag.hxx"
+#include "Compiler.h"
+
+#include <string>
+#include <utility>
+
+#include <time.h>
+
+struct 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 in milliseconds.
+ */
+ unsigned start_ms;
+
+ /**
+ * End of this sub-song within the file in milliseconds.
+ * Unused if zero.
+ */
+ unsigned end_ms;
+
+ explicit DetachedSong(const LightSong &other);
+
+public:
+ explicit DetachedSong(const DetachedSong &) = default;
+
+ explicit DetachedSong(const char *_uri)
+ :uri(_uri),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ explicit DetachedSong(const std::string &_uri)
+ :uri(_uri),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ explicit DetachedSong(std::string &&_uri)
+ :uri(std::move(_uri)),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ template<typename U>
+ DetachedSong(U &&_uri, Tag &&_tag)
+ :uri(std::forward<U>(_uri)),
+ tag(std::move(_tag)),
+ mtime(0), start_ms(0), end_ms(0) {}
+
+ DetachedSong(DetachedSong &&) = 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;
+ }
+
+ unsigned GetStartMS() const {
+ return start_ms;
+ }
+
+ void SetStartMS(unsigned _value) {
+ start_ms = _value;
+ }
+
+ unsigned GetEndMS() const {
+ return end_ms;
+ }
+
+ void SetEndMS(unsigned _value) {
+ end_ms = _value;
+ }
+
+ gcc_pure
+ double GetDuration() const;
+
+ /**
+ * 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 b2942588e..000000000
--- a/src/Directory.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 "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)
-{
- assert(path != nullptr);
-
- const size_t path_size = strlen(path) + 1;
- Directory *directory =
- (Directory *)g_malloc0(sizeof(*directory)
- - sizeof(directory->path)
- + path_size);
- new(directory) Directory(path);
-
- return directory;
-}
-
-Directory::Directory()
-{
- INIT_LIST_HEAD(&children);
- INIT_LIST_HEAD(&songs);
-
- path[0] = 0;
-}
-
-Directory::Directory(const char *_path)
-{
- 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 28a0aad1a..000000000
--- a/src/InputStream.cxx
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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)
-{
- assert(ptr != nullptr);
- assert(_size > 0);
-
- return plugin.read(this, ptr, _size, error);
-}
-
-size_t
-InputStream::LockRead(void *ptr, size_t _size, Error &error)
-{
- assert(ptr != nullptr);
- 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..3cd907179
--- /dev/null
+++ b/src/LogBackend.cxx
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "LogBackend.hxx"
+#include "Log.hxx"
+#include "util/Domain.hxx"
+#include "util/CharUtil.hxx"
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_SYSLOG
+#include <syslog.h>
+#endif
+
+#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);
+
+ while (length > 0 && IsWhitespaceOrNull(p[length - 1]))
+ --length;
+
+ return (int)length;
+}
+
+#ifdef HAVE_SYSLOG
+
+static int
+ToSysLogLevel(LogLevel log_level)
+{
+ switch (log_level) {
+ case LogLevel::DEBUG:
+ return LOG_DEBUG;
+
+ case LogLevel::INFO:
+ return LOG_INFO;
+
+ case LogLevel::DEFAULT:
+ return LOG_NOTICE;
+
+ case LogLevel::WARNING:
+ return LOG_WARNING;
+
+ case LogLevel::ERROR:
+ return LOG_ERR;
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+static void
+SysLog(const Domain &domain, LogLevel log_level, const char *message)
+{
+ syslog(ToSysLogLevel(log_level), "%s: %.*s",
+ domain.GetName(),
+ chomp_length(message), message);
+}
+
+void
+LogInitSysLog()
+{
+ openlog(PACKAGE, 0, LOG_DAEMON);
+ enable_syslog = true;
+}
+
+void
+LogFinishSysLog()
+{
+ if (enable_syslog)
+ closelog();
+}
+
+#endif
+
+static void
+FileLog(const Domain &domain, const char *message)
+{
+#ifdef HAVE_GLIB
+ char *converted;
+
+ if (log_charset != nullptr) {
+ converted = g_convert_with_fallback(message, -1,
+ log_charset, "utf-8",
+ nullptr, nullptr,
+ nullptr, nullptr);
+ if (converted != nullptr)
+ message = converted;
+ } else
+ converted = nullptr;
+#endif
+
+ fprintf(stderr, "%s%s: %.*s\n",
+ enable_timestamp ? log_date() : "",
+ domain.GetName(),
+ chomp_length(message), message);
+
+#ifdef HAVE_GLIB
+ g_free(converted);
+#endif
+}
+
+#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..accd1d4d8 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,63 @@ 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)
{
+#ifdef ANDROID
+ (void)verbose;
+#else
if (verbose)
- log_threshold = G_LOG_LEVEL_DEBUG;
-
- log_init_stdout();
+ 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 +148,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 +157,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 +166,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 +243,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..e914d876c 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,86 @@
#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 "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 +113,23 @@
#include <ws2tcpip.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 +147,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
+
+static bool
+InitStorage(Error &error)
+{
+ Storage *storage = CreateConfiguredStorage(error);
+ if (storage == nullptr)
+ return !error.IsDefined();
+
+ assert(!error.IsDefined());
- mapper_init(std::move(music_dir), std::move(playlist_dir));
+ CompositeStorage *composite = new CompositeStorage();
+ instance->storage = composite;
+ composite->Mount("", storage);
return true;
}
@@ -156,50 +185,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;
+ return true;
+ }
+ } else {
+ if (IsStorageConfigured())
+ LogDefault(config_domain,
+ "Ignoring the storage configuration "
+ "because the database does not need it");
}
- 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 (!instance->database->Open(error))
+ FatalError(error);
- if (param == nullptr)
+ if (!instance->database->IsPlugin(simple_db_plugin))
return true;
- Error error;
- if (!DatabaseGlobalInit(*param, error))
- FatalError(error);
-
- delete allocated;
-
- 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 +263,24 @@ 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;
+
+#ifdef ANDROID
+ const auto cache_dir = GetUserCacheDir();
+ if (cache_dir.IsNull())
+ return true;
+
+ path_fs = AllocatedPath::Build(cache_dir, "state");
+#else
+ return true;
+#endif
+ }
state_file = new StateFile(std::move(path_fs),
- *instance->partition, *main_loop);
+ *instance->partition,
+ *instance->event_loop);
state_file->Read();
return true;
}
@@ -240,9 +292,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 +311,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 +327,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 +347,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 +386,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 +402,17 @@ int main(int argc, char *argv[])
#endif
}
+#endif
+
+#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 +420,42 @@ 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 (!ReadConfigFile(config_path, error)) {
+ LogError(error);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+#else
+ if (!parse_cmdline(argc, argv, &options, error)) {
LogError(error);
return EXIT_FAILURE;
}
@@ -386,6 +464,7 @@ int mpd_main(int argc, char *argv[])
LogError(error);
return EXIT_FAILURE;
}
+#endif
stats_global_init();
TagLoadConfig();
@@ -395,23 +474,39 @@ 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
- GlobalEvents::Initialize(*main_loop);
+ GlobalEvents::Initialize(*instance->event_loop);
GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
#ifdef WIN32
GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted);
@@ -431,23 +526,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 +553,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);
+#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 +617,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 +630,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 +647,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 +673,53 @@ 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;
+#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);
+}
+
+#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..26dc11591 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
diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx
index d2b23d43a..84e2af1d1 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
diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx
index 2d20ac7ac..899163485 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
diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx
index ecd57090b..97fc860d9 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
diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx
index a5bbe590e..d7e36f2a4 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
diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx
index f2db33cc5..3af77e60f 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
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 30d3ba30f..000000000
--- a/src/OutputThread.cxx
+++ /dev/null
@@ -1,679 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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;
-
- void *dest = ao->cross_fade_buffer.Get(other_length);
- memcpy(dest, other_data, other_length);
- if (!pcm_mix(dest, data, length,
- ao->in_audio_format.format,
- 1.0 - chunk->mix_ratio)) {
- 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..4341a9ed3 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,27 @@
#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"
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 +48,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 +79,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);
}
@@ -166,11 +173,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 +191,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..244b64f5c 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,15 +221,13 @@ PlayerControl::EnqueueSong(Song *song)
}
bool
-PlayerControl::Seek(Song *song, float seek_time)
+PlayerControl::Seek(DetachedSong *song, float seek_time)
{
assert(song != nullptr);
Lock();
- if (next_song != nullptr)
- next_song->Free();
-
+ delete next_song;
next_song = song;
seek_where = seek_time;
SynchronousCommand(PlayerCommand::SEEK);
diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx
index 61bb408d2..b60227d23 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
@@ -29,7 +29,9 @@
#include <stdint.h>
-struct Song;
+class PlayerListener;
+class MultipleOutputs;
+class DetachedSong;
enum class PlayerState : uint8_t {
STOP,
@@ -46,7 +48,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,
@@ -91,6 +93,10 @@ struct player_status {
};
struct PlayerControl {
+ PlayerListener &listener;
+
+ MultipleOutputs &outputs;
+
unsigned buffer_chunks;
unsigned int buffered_before_play;
@@ -131,16 +137,16 @@ struct PlayerControl {
Error error;
/**
- * A copy of the current #Song after its tags have been
- * updated by the decoder (for example, a radio stream that
- * has sent a new tag after switching to the next song). This
- * shall be used by the GlobalEvents::TAG handler to update
- * the current #Song in the queue.
+ * A copy of the current #DetachedSong after its tags have
+ * been updated by the decoder (for example, a radio stream
+ * that has sent a new tag after switching to the next song).
+ * This shall be used by 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;
@@ -153,7 +159,7 @@ struct PlayerControl {
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
- Song *next_song;
+ DetachedSong *next_song;
double seek_where;
@@ -170,7 +176,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 +307,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 +379,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 +390,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 +399,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 +411,7 @@ public:
void UpdateAudio();
private:
- void EnqueueSongLocked(Song *song) {
+ void EnqueueSongLocked(DetachedSong *song) {
assert(song != nullptr);
assert(next_song == nullptr);
@@ -416,7 +424,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 +434,7 @@ public:
* @return true on success, false on failure (e.g. if MPD isn't
* playing currently)
*/
- bool Seek(Song *song, float seek_time);
+ bool Seek(DetachedSong *song, float seek_time);
void SetCrossFade(float cross_fade_seconds);
diff --git a/src/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..9f342ad5d 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,7 +125,7 @@ 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.
*/
@@ -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;
+ unsigned start_ms = pc.next_song->GetStartMS();
if (pc.command == PlayerCommand::SEEK)
start_ms += (unsigned)(pc.seek_where * 1000);
- dc.Start(pc.next_song->DupDetached(),
- start_ms, pc.next_song->end_ms,
+ dc.Start(new DetachedSong(*pc.next_song),
+ start_ms, pc.next_song->GetEndMS(),
buffer, _pipe);
}
@@ -330,7 +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,9 +340,7 @@ Player::WaitForDecoder()
pc.ClearTaggedSong();
- if (song != nullptr)
- song->Free();
-
+ delete song;
song = pc.next_song;
elapsed_time = 0.0;
@@ -361,7 +359,7 @@ Player::WaitForDecoder()
pc.Unlock();
/* call syncPlaylistWithQueue() in the main thread */
- GlobalEvents::Emit(GlobalEvents::PLAYLIST);
+ pc.listener.OnPlayerSync();
return true;
}
@@ -371,19 +369,20 @@ Player::WaitForDecoder()
* indicated by the decoder plugin.
*/
static double
-real_song_duration(const Song *song, double decoder_duration)
+real_song_duration(const DetachedSong &song, double decoder_duration)
{
- assert(song != nullptr);
-
if (decoder_duration <= 0.0)
/* the decoder plugin didn't provide information; fall
back to Song::GetDuration() */
- return song->GetDuration();
+ return song.GetDuration();
- if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration)
- return (song->end_ms - song->start_ms) / 1000.0;
+ const unsigned start_ms = song.GetStartMS();
+ const unsigned end_ms = song.GetEndMS();
- return decoder_duration - song->start_ms / 1000.0;
+ if (end_ms > 0 && end_ms / 1000.0 < decoder_duration)
+ return (end_ms - start_ms) / 1000.0;
+
+ return decoder_duration - start_ms / 1000.0;
}
bool
@@ -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;
}
@@ -505,7 +504,7 @@ Player::SendSilence()
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 unsigned start_ms = pc.next_song->GetStartMS();
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;
}
@@ -583,7 +582,7 @@ Player::SeekDecoder()
/* re-fill the buffer after seeking */
buffering = true;
- audio_output_all_cancel();
+ pc.outputs.Cancel();
return true;
}
@@ -600,7 +599,7 @@ Player::ProcessCommand()
case PlayerCommand::UPDATE_AUDIO:
pc.Unlock();
- audio_output_all_enable_disable();
+ pc.outputs.EnableDisable();
pc.Lock();
pc.CommandFinished();
break;
@@ -619,7 +618,7 @@ Player::ProcessCommand()
paused = !paused;
if (paused) {
- audio_output_all_pause();
+ pc.outputs.Pause();
pc.Lock();
pc.state = PlayerState::PAUSE;
@@ -661,7 +660,7 @@ Player::ProcessCommand()
pc.Lock();
}
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
queued = false;
pc.CommandFinished();
@@ -670,11 +669,11 @@ 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();
+ pc.elapsed_time = pc.outputs.GetElapsedTime();
if (pc.elapsed_time < 0.0)
pc.elapsed_time = elapsed_time;
@@ -684,23 +683,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 */
@@ -716,7 +712,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag)
*/
static bool
play_chunk(PlayerControl &pc,
- Song *song, struct music_chunk *chunk,
+ DetachedSong &song, struct music_chunk *chunk,
MusicBuffer &buffer,
const AudioFormat format,
Error &error)
@@ -737,7 +733,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,7 +744,7 @@ 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;
@@ -839,7 +835,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 +879,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;
@@ -940,7 +933,7 @@ Player::Run()
pc.command == PlayerCommand::EXIT ||
pc.command == PlayerCommand::CLOSE_AUDIO) {
pc.Unlock();
- audio_output_all_cancel();
+ pc.outputs.Cancel();
break;
}
@@ -956,7 +949,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 +1029,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 +1054,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 +1075,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 +1085,7 @@ Player::Run()
if (queued) {
assert(pc.next_song != nullptr);
- pc.next_song->Free();
+ delete pc.next_song;
pc.next_song = nullptr;
}
@@ -1115,6 +1107,8 @@ player_task(void *arg)
{
PlayerControl &pc = *(PlayerControl *)arg;
+ SetThreadName("player");
+
DecoderControl dc(pc.mutex, pc.cond);
decoder_thread_start(dc);
@@ -1130,22 +1124,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 +1145,7 @@ player_task(void *arg)
case PlayerCommand::CLOSE_AUDIO:
pc.Unlock();
- audio_output_all_release();
+ pc.outputs.Release();
pc.Lock();
pc.CommandFinished();
@@ -1164,7 +1156,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 +1166,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;
diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx
index efdcf05ca..7e4150252 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
diff --git a/src/Playlist.cxx b/src/Playlist.cxx
deleted file mode 100644
index 8d9ab24a3..000000000
--- a/src/Playlist.cxx
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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;
-
- 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 = GetCurrentPosition();
-
- queue.ShuffleOrder();
-
- if (current_position >= 0) {
- /* make sure the current song is the first in
- the order list, so the whole rest of the
- playlist is played after that */
- unsigned current_order =
- queue.PositionToOrder(current_position);
- queue.SwapOrders(0, current_order);
- current = 0;
- } else
- current = -1;
- } else
- playlist_order(*this);
-
- UpdateQueuedSong(pc, queued_song);
-
- idle_add(IDLE_OPTIONS);
-}
-
-int
-playlist::GetCurrentPosition() const
-{
- return current >= 0
- ? queue.OrderToPosition(current)
- : -1;
-}
-
-int
-playlist::GetNextPosition() const
-{
- if (current < 0)
- return -1;
-
- if (queue.single && queue.repeat)
- return queue.OrderToPosition(current);
- else if (queue.IsValidOrder(current + 1))
- return queue.OrderToPosition(current + 1);
- else if (queue.repeat)
- return queue.OrderToPosition(0);
-
- return -1;
-}
diff --git a/src/Playlist.hxx b/src/Playlist.hxx
deleted file mode 100644
index 7d7e9b154..000000000
--- a/src/Playlist.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_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;
-
- /**
- * Number of errors since playback was started. If this
- * number exceeds the length of the playlist, MPD gives up,
- * because all songs have been tried.
- */
- unsigned error_count;
-
- /**
- * The "current song pointer". This is the song which is
- * played when we get the "play" command. It is also the song
- * which is currently being played.
- */
- int current;
-
- /**
- * The "next" song to be played, when the current one
- * finishes. The decoder thread may start decoding and
- * buffering it, while the "current" song is still playing.
- *
- * This variable is only valid if #playing is true.
- */
- int queued;
-
- playlist(unsigned max_length)
- :queue(max_length), playing(false), current(-1), queued(-1) {
- }
-
- ~playlist() {
- }
-
- uint32_t GetVersion() const {
- return queue.version;
- }
-
- unsigned GetLength() const {
- return queue.GetLength();
- }
-
- unsigned PositionToId(unsigned position) const {
- return queue.PositionToId(position);
- }
-
- gcc_pure
- int GetCurrentPosition() const;
-
- gcc_pure
- int GetNextPosition() const;
-
- /**
- * Returns the song object which is currently queued. Returns
- * none if there is none (yet?) or if MPD isn't playing.
- */
- gcc_pure
- const Song *GetQueuedSong() const;
-
- /**
- * This is the "PLAYLIST" event handler. It is invoked by the
- * player thread whenever it requests a new queued song, or
- * when it exits.
- */
- void SyncWithPlayer(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 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 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 2dbd75d6e..000000000
--- a/src/PlaylistControl.cxx
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 playlist_play_order() will
- discard them anyway */
- }
-
- PlayOrder(pc, next_order);
- }
-
- /* Consume mode removes each played songs. */
- if (queue.consume)
- DeleteOrder(pc, old_current);
-}
-
-void
-playlist::PlayPrevious(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::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
-{
- if (!queue.IsValidPosition(song))
- return PlaylistResult::BAD_RANGE;
-
- const Song *queued_song = GetQueuedSong();
-
- unsigned i = queue.random
- ? queue.PositionToOrder(song)
- : song;
-
- pc.ClearError();
- stop_on_error = true;
- error_count = 0;
-
- if (!playing || (unsigned)current != i) {
- /* seeking is not within the current song - prepare
- song change */
-
- playing = true;
- current = i;
-
- queued_song = nullptr;
- }
-
- Song *the_song = queue.GetOrder(i).DupDetached();
- if (!pc.Seek(the_song, seek_time)) {
- UpdateQueuedSong(pc, queued_song);
-
- return PlaylistResult::NOT_PLAYING;
- }
-
- queued = -1;
- UpdateQueuedSong(pc, nullptr);
-
- return PlaylistResult::SUCCESS;
-}
-
-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 SeekSongPosition(pc, current, seek_time);
-}
diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx
index a6d15e755..81aeae2cd 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,8 @@
#include "config.h"
#include "PlaylistDatabase.hxx"
-#include "PlaylistVector.hxx"
-#include "TextFile.hxx"
+#include "db/PlaylistVector.hxx"
+#include "fs/TextFile.hxx"
#include "util/StringUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx
index 1481f621f..48de64efa 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
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
deleted file mode 100644
index 3eea2491e..000000000
--- a/src/PlaylistEdit.cxx
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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()
-{
- queue.IncrementVersion();
-
- idle_add(IDLE_PLAYLIST);
-}
-
-void
-playlist::Clear(PlayerControl &pc)
-{
- Stop(pc);
-
- queue.Clear();
- current = -1;
-
- 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 e7dae6258..f5ac2735f 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/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>
@@ -141,8 +136,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);
@@ -151,7 +146,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);
@@ -159,10 +154,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;
@@ -248,9 +242,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;
@@ -259,6 +254,9 @@ LoadPlaylistFile(const char *utf8path, Error &error)
} else
continue;
}
+#else
+ continue;
+#endif
} else {
uri_utf8 = PathToUTF8(s);
if (uri_utf8.empty())
@@ -365,7 +363,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;
@@ -403,26 +401,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 9afbe349d..000000000
--- a/src/PlaylistRegistry.cxx
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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)
-{
- const char *suffix;
- SongEnumerator *playlist = nullptr;
-
- assert(uri != nullptr);
-
- suffix = uri_get_suffix(uri);
- 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)
-{
- const char *suffix;
-
- 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;
- }
-
- suffix = uri != nullptr ? uri_get_suffix(uri) : 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 235dfe7a0..0640ae19e 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>
@@ -52,20 +53,10 @@ locate_parse_type(const char *str)
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;
-}
-
-gcc_pure
-static std::string
ImportString(const char *p, bool fold_case)
{
return fold_case
- ? CaseFold(p)
+ ? IcuCaseFold(p)
: std::string(p);
}
@@ -81,10 +72,8 @@ SongFilter::Item::StringMatch(const char *s) const
assert(s != nullptr);
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;
}
@@ -136,7 +125,19 @@ 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_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();
@@ -148,7 +149,7 @@ SongFilter::Item::Match(const Song &song) const
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)
@@ -181,20 +182,30 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
}
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..ca7d7bd90 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
@@ -35,9 +35,12 @@
#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:
@@ -79,7 +82,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 +102,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..f607fc151 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,35 +19,61 @@
#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 "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)
client_printf(client, "Range: %u.%03u-%u.%03u\n",
@@ -63,6 +89,30 @@ song_print_info(Client &client, const Song &song)
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.GetStartMS();
+ const unsigned end_ms = song.GetEndMS();
+
+ if (end_ms > 0)
+ client_printf(client, "Range: %u.%03u-%u.%03u\n",
+ start_ms / 1000,
+ start_ms % 1000,
+ end_ms / 1000,
+ end_ms % 1000);
+ else if (start_ms > 0)
+ client_printf(client, "Range: %u.%03u-\n",
+ start_ms / 1000,
+ start_ms % 1000);
+
+ if (song.GetLastModified() > 0)
+ time_print(client, "Last-Modified", song.GetLastModified());
+
+ tag_print(client, song.GetTag());
}
diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx
index f8df89d38..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..d6c1dbdd7 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,10 @@
#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/TextFile.hxx"
#include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include "util/StringUtil.hxx"
@@ -37,41 +37,55 @@
static constexpr Domain song_save_domain("song_save");
+static void
+range_save(FILE *file, unsigned start_ms, unsigned end_ms)
+{
+ if (end_ms > 0)
+ fprintf(file, "Range: %u-%u\n", start_ms, end_ms);
+ else if (start_ms > 0)
+ fprintf(file, "Range: %u-\n", start_ms);
+}
+
void
song_save(FILE *fp, const Song &song)
{
fprintf(fp, SONG_BEGIN "%s\n", song.uri);
- if (song.end_ms > 0)
- fprintf(fp, "Range: %u-%u\n", song.start_ms, song.end_ms);
- else if (song.start_ms > 0)
- fprintf(fp, "Range: %u-\n", song.start_ms);
+ range_save(fp, song.start_ms, song.end_ms);
- if (song.tag != nullptr)
- tag_save(fp, *song.tag);
+ tag_save(fp, song.tag);
fprintf(fp, SONG_MTIME ": %li\n", (long)song.mtime);
fprintf(fp, SONG_END "\n");
}
-Song *
-song_load(TextFile &file, Directory *parent, const char *uri,
+void
+song_save(FILE *fp, const DetachedSong &song)
+{
+ fprintf(fp, SONG_BEGIN "%s\n", song.GetURI());
+
+ range_save(fp, song.GetStartMS(), song.GetEndMS());
+
+ tag_save(fp, song.GetTag());
+
+ fprintf(fp, SONG_MTIME ": %li\n", (long)song.GetLastModified());
+ fprintf(fp, SONG_END "\n");
+}
+
+DetachedSong *
+song_load(TextFile &file, const char *uri,
Error &error)
{
- Song *song = parent != nullptr
- ? Song::NewFile(uri, parent)
- : Song::NewRemote(uri);
- char *line, *colon;
- TagType type;
- const char *value;
+ DetachedSong *song = new DetachedSong(uri);
TagBuilder tag;
+ char *line;
while ((line = file.ReadLine()) != nullptr &&
strcmp(line, SONG_END) != 0) {
- colon = strchr(line, ':');
+ char *colon = strchr(line, ':');
if (colon == nullptr || colon == line) {
- song->Free();
+ delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
@@ -79,8 +93,9 @@ song_load(TextFile &file, Directory *parent, const char *uri,
}
*colon++ = 0;
- value = strchug_fast(colon);
+ const char *value = strchug_fast(colon);
+ TagType type;
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) {
@@ -88,15 +103,19 @@ song_load(TextFile &file, Directory *parent, const char *uri,
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {
- song->mtime = atoi(value);
+ song->SetLastModified(atoi(value));
} else if (strcmp(line, "Range") == 0) {
char *endptr;
- song->start_ms = strtoul(value, &endptr, 10);
- if (*endptr == '-')
- song->end_ms = strtoul(endptr + 1, nullptr, 10);
+ unsigned start_ms = strtoul(value, &endptr, 10);
+ unsigned end_ms = *endptr == '-'
+ ? strtoul(endptr + 1, nullptr, 10)
+ : 0;
+
+ song->SetStartMS(start_ms);
+ song->SetEndMS(end_ms);
} else {
- song->Free();
+ delete song;
error.Format(song_save_domain,
"unknown line in db: %s", line);
@@ -104,8 +123,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..2a0edb49d 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
@@ -26,12 +26,16 @@
struct Song;
struct Directory;
+class DetachedSong;
class TextFile;
class Error;
void
song_save(FILE *fp, const Song &song);
+void
+song_save(FILE *fp, const DetachedSong &song);
+
/**
* Loads a song from the input file. Reading stops after the
* "song_end" line.
@@ -39,8 +43,8 @@ song_save(FILE *fp, const Song &song);
* @param error location to store the error occurring
* @return true on success, false on error
*/
-Song *
-song_load(TextFile &file, Directory *parent, const char *uri,
+DetachedSong *
+song_load(TextFile &file, const char *uri,
Error &error);
#endif
diff --git a/src/SongSort.cxx b/src/SongSort.cxx
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..a3069605f 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,13 @@
#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/TextFile.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"
@@ -75,7 +76,7 @@ StateFile::Write()
}
save_sw_volume_state(fp);
- audio_output_state_save(fp);
+ audio_output_state_save(fp, partition.outputs);
playlist_state_save(fp, partition.playlist, partition.pc);
fclose(fp);
@@ -97,11 +98,19 @@ StateFile::Read()
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,
+ 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,
diff --git a/src/StateFile.hxx b/src/StateFile.hxx
index 4ec2c4be7..e35797b95 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
diff --git a/src/Stats.cxx b/src/Stats.cxx
index f224bdf49..8fc626ecb 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,58 +20,84 @@
#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 (!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();
+ if (!stats_update(db))
+ return;
client_printf(client,
"artists: %u\n"
@@ -83,22 +109,31 @@ db_stats_print(Client &client)
stats.song_count,
stats.total_duration);
- 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..84faa848a 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,76 @@
#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 "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 = InputStream::OpenReady(path_fs.c_str(),
+ mutex, cond,
+ IgnoreError());
if (is == nullptr)
- is = InputStream::Open(path_fs.c_str(),
- mutex, cond,
- IgnoreError());
+ return false;
+ } else
+ is->LockRewind(IgnoreError());
+
+ /* now try the stream_tag() method */
+ return plugin.ScanStream(*is, handler, handler_ctx);
+ }
- /* now try the stream_tag() method */
- if (is != nullptr) {
- if (plugin->ScanStream(*is,
- *handler, handler_ctx))
- break;
+ bool Scan(const DecoderPlugin &plugin) {
+ return plugin.SupportsSuffix(suffix) &&
+ (ScanFile(plugin) || ScanStream(plugin));
+ }
+};
- is->LockRewind(IgnoreError());
- }
- }
+bool
+tag_file_scan(Path path_fs, const tag_handler &handler, void *handler_ctx)
+{
+ assert(!path_fs.IsNull());
- plugin = decoder_plugin_from_suffix(suffix, plugin);
- } while (plugin != nullptr);
+ /* check if there's a suffix and a plugin */
- if (is != nullptr)
- is->Close();
+ const char *suffix = uri_get_suffix(path_fs.c_str());
+ if (suffix == nullptr)
+ return false;
- return plugin != nullptr;
+ TagFileScan tfs(path_fs, suffix, handler, handler_ctx);
+ return decoder_plugins_try([&](const DecoderPlugin &plugin){
+ return tfs.Scan(plugin);
+ });
}
diff --git a/src/TagFile.hxx b/src/TagFile.hxx
index 078abebd9..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..228b5fd90 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,6 +36,12 @@ void tag_print_types(Client &client)
}
}
+void
+tag_print(Client &client, TagType type, const char *value)
+{
+ client_printf(client, "%s: %s\n", tag_item_names[type], value);
+}
+
void tag_print(Client &client, const Tag &tag)
{
if (tag.time >= 0)
diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx
index ccc0c9aa4..20e7f3288 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,19 @@
#ifndef MPD_TAG_PRINT_HXX
#define MPD_TAG_PRINT_HXX
+#include <stdint.h>
+
+enum TagType : uint8_t;
+
struct Tag;
class Client;
void tag_print_types(Client &client);
void
+tag_print(Client &client, TagType type, const char *value);
+
+void
tag_print(Client &client, const Tag &tag);
#endif
diff --git a/src/TagSave.cxx b/src/TagSave.cxx
index b20d986c2..3a291e115 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,7 +20,8 @@
#include "config.h"
#include "TagSave.hxx"
#include "tag/Tag.hxx"
-#include "Song.hxx"
+
+#define SONG_TIME "Time: "
void
tag_save(FILE *file, const Tag &tag)
diff --git a/src/TagSave.hxx b/src/TagSave.hxx
index 0b1359c89..d209c0a16 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
diff --git a/src/TagStream.cxx b/src/TagStream.cxx
new file mode 100644
index 000000000..639763373
--- /dev/null
+++ b/src/TagStream.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 "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());
+
+ const char *const suffix = uri_get_suffix(is.GetURI());
+ 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/Win32Main.cxx b/src/Win32Main.cxx
deleted file mode 100644
index 0d2a70348..000000000
--- a/src/Win32Main.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 "Main.hxx"
-
-#ifdef WIN32
-
-#include "Compiler.h"
-#include "GlobalEvents.hxx"
-#include "system/FatalError.hxx"
-
-#include <cstdlib>
-#include <atomic>
-
-#include <glib.h>
-
-#include <windows.h>
-
-static int service_argc;
-static char **service_argv;
-static char service_name[] = "";
-static std::atomic_bool running;
-static SERVICE_STATUS_HANDLE service_handle;
-
-static void WINAPI
-service_main(DWORD argc, CHAR *argv[]);
-
-static SERVICE_TABLE_ENTRY service_registry[] = {
- {service_name, service_main},
- {nullptr, nullptr}
-};
-
-static void
-service_notify_status(DWORD status_code)
-{
- SERVICE_STATUS current_status;
-
- current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
- current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
- ? 0
- : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
-
- current_status.dwCurrentState = status_code;
- current_status.dwWin32ExitCode = NO_ERROR;
- current_status.dwCheckPoint = 0;
- current_status.dwWaitHint = 1000;
-
- SetServiceStatus(service_handle, &current_status);
-}
-
-static DWORD WINAPI
-service_dispatcher(gcc_unused DWORD control, gcc_unused DWORD event_type,
- gcc_unused void *event_data, gcc_unused void *context)
-{
- switch (control) {
- case SERVICE_CONTROL_SHUTDOWN:
- case SERVICE_CONTROL_STOP:
- GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
- return NO_ERROR;
- default:
- return NO_ERROR;
- }
-}
-
-static void WINAPI
-service_main(gcc_unused DWORD argc, gcc_unused CHAR *argv[])
-{
- DWORD error_code;
- gchar* error_message;
-
- service_handle =
- RegisterServiceCtrlHandlerEx(service_name,
- service_dispatcher, nullptr);
-
- if (service_handle == 0) {
- error_code = GetLastError();
- error_message = g_win32_error_message(error_code);
- FormatFatalError("RegisterServiceCtrlHandlerEx() failed: %s",
- error_message);
- }
-
- service_notify_status(SERVICE_START_PENDING);
- mpd_main(service_argc, service_argv);
- service_notify_status(SERVICE_STOPPED);
-}
-
-static BOOL WINAPI
-console_handler(DWORD event)
-{
- switch (event) {
- case CTRL_C_EVENT:
- case CTRL_CLOSE_EVENT:
- if (running.load()) {
- // Recent msdn docs that process is terminated
- // if this function returns TRUE.
- // We initiate correct shutdown sequence (if possible).
- // Once main() returns CRT will terminate our process
- // regardless our thread is still active.
- // If this did not happen within 3 seconds
- // let's shutdown anyway.
- GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
- // Under debugger it's better to wait indefinitely
- // to allow debugging of shutdown code.
- Sleep(IsDebuggerPresent() ? INFINITE : 3000);
- }
- // If we're not running main loop there is no chance for
- // clean shutdown.
- std::exit(EXIT_FAILURE);
- return TRUE;
- default:
- return FALSE;
- }
-}
-
-int win32_main(int argc, char *argv[])
-{
- DWORD error_code;
- gchar* error_message;
-
- service_argc = argc;
- service_argv = argv;
-
- if (StartServiceCtrlDispatcher(service_registry))
- return 0; /* run as service successefully */
-
- error_code = GetLastError();
- if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
- /* running as console app */
- running.store(false);
- SetConsoleTitle("Music Player Daemon");
- SetConsoleCtrlHandler(console_handler, TRUE);
- return mpd_main(argc, argv);
- }
-
- error_message = g_win32_error_message(error_code);
- FormatFatalError("StartServiceCtrlDispatcher() failed: %s",
- error_message);
-}
-
-void win32_app_started()
-{
- if (service_handle != 0)
- service_notify_status(SERVICE_RUNNING);
- else
- running.store(true);
-}
-
-void win32_app_stopping()
-{
- if (service_handle != 0)
- service_notify_status(SERVICE_STOP_PENDING);
- else
- running.store(false);
-}
-
-#endif
diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx
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 973fe91dc..000000000
--- a/src/archive/ZzipArchivePlugin.cxx
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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 != -1) {
- error.Set(zzip_domain, "zzip_seek() has failed");
- is->offset = ofs;
- return true;
- }
- return false;
-}
-
-/* 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..14a68fdb5
--- /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 "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 = InputStream::OpenReady(pathname.c_str(), 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..396055c71
--- /dev/null
+++ b/src/archive/plugins/ZzipArchivePlugin.cxx
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 (InputPlugin::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 != -1) {
+ error.Set(zzip_domain, "zzip_seek() has failed");
+ offset = ofs;
+ return true;
+ }
+ return false;
+}
+
+/* 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..849a11ed4
--- /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..eba64d09c
--- /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..1ec6d29d5
--- /dev/null
+++ b/src/client/ClientRead.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 "ClientInternal.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "event/Loop.hxx"
+#include "util/CharUtil.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 */
+ 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();
+ 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..6143dacdf 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 },
@@ -139,9 +161,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 +184,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 +196,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 +224,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 +233,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 +285,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 +319,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;
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 cc10f7205..1dcbf2946 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 b86cbdae7..70d97a63e 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,46 +19,58 @@
#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 <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;
}
@@ -66,28 +78,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;
}
@@ -100,51 +114,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 = "";
@@ -152,30 +188,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",
@@ -183,21 +220,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);
@@ -207,7 +268,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 = "";
@@ -215,7 +276,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..0a4f9592a 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,108 @@
* 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 "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 +165,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 +218,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..c9b8a46f3 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,37 @@
#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/update/Service.hxx"
#endif
#include <assert.h>
@@ -64,7 +68,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 +78,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 +86,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 +94,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
+}
- if (argc == 2)
- uri = argv[1];
- else
+static constexpr tag_handler print_tag_handler = {
+ nullptr,
+ print_tag,
+ nullptr,
+};
+
+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,39 +175,61 @@ 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[])
+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) {
@@ -173,7 +245,13 @@ handle_update(Client &client, gcc_unused int argc, char *argv[])
}
}
- ret = update_enqueue(path, false);
+ UpdateService *update = client.partition.instance.update;
+ if (update == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ unsigned ret = update->Enqueue(path, discard);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK;
@@ -182,38 +260,31 @@ handle_update(Client &client, gcc_unused int argc, char *argv[])
"already updating");
return CommandResult::ERROR;
}
+#else
+ (void)client;
+ (void)argc;
+ (void)argv;
+ (void)discard;
+
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
}
CommandResult
-handle_rescan(Client &client, gcc_unused int argc, char *argv[])
+handle_update(Client &client, gcc_unused unsigned argc, char *argv[])
{
- const char *path = "";
- unsigned ret;
-
- assert(argc <= 2);
- if (argc == 2) {
- path = argv[1];
-
- if (!uri_safe_local(path)) {
- command_error(client, ACK_ERROR_ARG,
- "Malformed path");
- return CommandResult::ERROR;
- }
- }
+ return handle_update(client, argc, argv, false);
+}
- 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;
- }
+CommandResult
+handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[])
+{
+ return handle_update(client, argc, argv, true);
}
CommandResult
-handle_setvol(Client &client, gcc_unused int argc, char *argv[])
+handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned level;
bool success;
@@ -226,7 +297,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 +308,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 +319,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 +331,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 +343,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 +351,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 +373,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 +381,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..f167b0edb 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(),
@@ -188,11 +190,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 +221,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 +238,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 +256,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 +267,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,27 +278,27 @@ 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;
@@ -305,7 +313,7 @@ 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;
@@ -320,7 +328,7 @@ 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 == '-';
@@ -334,7 +342,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 +354,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 +366,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 +379,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 +387,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 d178fa097..79bfb44d8 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,25 +19,24 @@
#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 "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)
{
@@ -50,14 +49,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;
@@ -67,36 +66,19 @@ handle_load(Client &client, int argc, char *argv[])
} else if (!check_range(client, &start_index, &end_index, argv[2]))
return CommandResult::ERROR;
- 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;
@@ -109,7 +91,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;
@@ -121,7 +103,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)
@@ -130,7 +112,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)
@@ -140,7 +122,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;
@@ -154,7 +136,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;
@@ -171,7 +153,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)
@@ -180,7 +162,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];
@@ -188,16 +170,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,
@@ -210,7 +197,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 a21eb75f0..0d0cff5cf 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,17 +20,18 @@
#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 "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "fs/AllocatedPath.hxx"
@@ -39,88 +40,69 @@
#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];
- PlaylistResult result;
+ 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 *const uri = translate_uri(client, argv[1]);
+ 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);
- 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;
- }
-
- result = client.partition.AppendURI(uri);
- return print_playlist_result(client, result);
+ return CommandResult::OK;
}
+#ifdef ENABLE_DATABASE
const DatabaseSelection selection(uri, true);
Error error;
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);
@@ -134,7 +116,7 @@ handle_addid(Client &client, int argc, char *argv[])
}
CommandResult
-handle_delete(Client &client, gcc_unused int argc, char *argv[])
+handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
{
unsigned start, end;
@@ -146,7 +128,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;
@@ -159,7 +141,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;
@@ -167,7 +149,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]))
@@ -179,14 +161,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;
@@ -198,7 +180,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;
@@ -210,7 +192,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;
@@ -227,7 +209,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;
@@ -247,11 +229,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;
}
@@ -261,19 +245,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;
@@ -286,7 +270,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]))
@@ -304,7 +288,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;
@@ -317,7 +301,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;
@@ -332,7 +316,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;
@@ -348,7 +332,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;
@@ -362,7 +346,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;
@@ -377,7 +361,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..ece543cfd 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,60 @@
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_delete(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_deleteid(Client &client, int argc, char *argv[]);
+handle_deleteid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlist(Client &client, int argc, char *argv[]);
+handle_playlist(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_shuffle(Client &client, int argc, char *argv[]);
+handle_shuffle(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_clear(Client &client, int argc, char *argv[]);
+handle_clear(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_plchanges(Client &client, int argc, char *argv[]);
+handle_plchanges(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_plchangesposid(Client &client, int argc, char *argv[]);
+handle_plchangesposid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistinfo(Client &client, int argc, char *argv[]);
+handle_playlistinfo(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistid(Client &client, int argc, char *argv[]);
+handle_playlistid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistfind(Client &client, int argc, char *argv[]);
+handle_playlistfind(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_playlistsearch(Client &client, int argc, char *argv[]);
+handle_playlistsearch(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_prio(Client &client, int argc, char *argv[]);
+handle_prio(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_prioid(Client &client, int argc, char *argv[]);
+handle_prioid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_move(Client &client, int argc, char *argv[]);
+handle_move(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_moveid(Client &client, int argc, char *argv[]);
+handle_moveid(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_swap(Client &client, int argc, char *argv[]);
+handle_swap(Client &client, unsigned argc, char *argv[]);
CommandResult
-handle_swapid(Client &client, int argc, char *argv[]);
+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..aeec73e1c
--- /dev/null
+++ b/src/command/StorageCommands.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.
+ */
+
+#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 "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(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(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..f045213a4
--- /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 = 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/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..506c9e9dc
--- /dev/null
+++ b/src/config/ConfigOption.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_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_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..8eaa22bdd
--- /dev/null
+++ b/src/config/ConfigTemplates.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 "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 },
+ { "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..ec3eacd1f
--- /dev/null
+++ b/src/db/Count.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 "Count.hxx"
+#include "Selection.hxx"
+#include "Interface.hxx"
+#include "client/Client.hxx"
+#include "LightSong.hxx"
+#include "tag/Set.hxx"
+
+#include <functional>
+#include <map>
+
+struct SearchStats {
+ unsigned n_songs;
+ unsigned long total_time_s;
+
+ constexpr SearchStats()
+ :n_songs(0), total_time_s(0) {}
+};
+
+class TagCountMap : public std::map<std::string, SearchStats> {
+};
+
+static void
+PrintSearchStats(Client &client, const SearchStats &stats)
+{
+ client_printf(client,
+ "songs: %u\n"
+ "playtime: %lu\n",
+ stats.n_songs, stats.total_time_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++;
+ stats.total_time_s += song.GetDuration();
+
+ return true;
+}
+
+static bool
+CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
+{
+ bool found = false;
+ for (unsigned i = 0; i < tag.num_items; ++i) {
+ const TagItem &item = *tag.items[i];
+
+ if (item.type == group) {
+ auto r = map.insert(std::make_pair(item.value,
+ SearchStats()));
+ SearchStats &s = r.first->second;
+ ++s.n_songs;
+ if (tag.time > 0)
+ s.total_time_s += tag.time;
+
+ 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..f6663311e
--- /dev/null
+++ b/src/db/DatabasePrint.cxx
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 (unsigned i = 0, n = tag.num_items; i < n; i++) {
+ const TagItem &item = *tag.items[i];
+ 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..699213835
--- /dev/null
+++ b/src/db/DatabaseSong.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 "DatabaseSong.hxx"
+#include "LightSong.hxx"
+#include "Interface.hxx"
+#include "DetachedSong.hxx"
+#include "storage/StorageInterface.hxx"
+
+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..b849e73e8
--- /dev/null
+++ b/src/db/Helpers.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 "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.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:
+#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..5fc9265e2
--- /dev/null
+++ b/src/db/Interface.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_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;
+
+ /**
+ * 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..af1e801f8
--- /dev/null
+++ b/src/db/LightSong.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 "LightSong.hxx"
+#include "tag/Tag.hxx"
+
+double
+LightSong::GetDuration() const
+{
+ if (end_ms > 0)
+ return (end_ms - start_ms) / 1000.0;
+
+ if (tag->time <= 0)
+ return 0;
+
+ return tag->time - start_ms / 1000.0;
+}
diff --git a/src/db/LightSong.hxx b/src/db/LightSong.hxx
new file mode 100644
index 000000000..add9da855
--- /dev/null
+++ b/src/db/LightSong.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_LIGHT_SONG_HXX
+#define MPD_LIGHT_SONG_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 in milliseconds.
+ */
+ unsigned start_ms;
+
+ /**
+ * End of this sub-song within the file in milliseconds.
+ * Unused if zero.
+ */
+ unsigned end_ms;
+
+ 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
+ double 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 cb1bcdc6b..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;
- virtual void ReturnSong(Song *song) const;
-
- virtual bool Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- VisitPlaylist visit_playlist,
- Error &error) const override;
-
- virtual bool VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const override;
-
- virtual bool GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats,
- Error &error) const override;
-
- virtual time_t GetUpdateStamp() const override {
- 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 nullptr;
-
- 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 nullptr;
-
- 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 nullptr;
-
- 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 dfe981dd8..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;
- virtual void ReturnSong(Song *song) const;
-
- virtual bool Visit(const DatabaseSelection &selection,
- VisitDirectory visit_directory,
- VisitSong visit_song,
- VisitPlaylist visit_playlist,
- Error &error) const override;
-
- virtual bool VisitUniqueTags(const DatabaseSelection &selection,
- TagType tag_type,
- VisitString visit_string,
- Error &error) const override;
-
- virtual bool GetStats(const DatabaseSelection &selection,
- DatabaseStats &stats,
- Error &error) const override;
-
- virtual time_t GetUpdateStamp() const override {
- 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..107af9d5f
--- /dev/null
+++ b/src/db/Stats.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_DATABASE_STATS_HXX
+#define MPD_DATABASE_STATS_HXX
+
+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;
+ }
+};
+
+#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..918e6be49
--- /dev/null
+++ b/src/db/plugins/ProxyDatabasePlugin.cxx
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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;
+ 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 {
+ 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 },
+ { 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_ms = mpd_song_get_start(song) * 1000;
+ end_ms = mpd_song_get_end(song) * 1000;
+#else
+ start_ms = end_ms = 0;
+#endif
+
+ TagBuilder tag_builder;
+ tag_builder.SetTime(mpd_song_get_duration(song));
+
+ 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 nullptr;
+
+ 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 nullptr;
+
+ 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);
+ 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 nullptr;
+
+ 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",
+ 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..62034a0b8
--- /dev/null
+++ b/src/db/plugins/simple/DatabaseSave.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 "DatabaseSave.hxx"
+#include "db/DatabaseLock.hxx"
+#include "db/DatabaseError.hxx"
+#include "Directory.hxx"
+#include "DirectorySave.hxx"
+#include "fs/TextFile.hxx"
+#include "tag/Tag.hxx"
+#include "tag/TagSettings.h"
+#include "fs/Charset.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <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(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 (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..3bd3377ae
--- /dev/null
+++ b/src/db/plugins/simple/DatabaseSave.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_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/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..9d3ebbac2
--- /dev/null
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "DirectorySave.hxx"
+#include "Directory.hxx"
+#include "Song.hxx"
+#include "SongSave.hxx"
+#include "DetachedSong.hxx"
+#include "PlaylistDatabase.hxx"
+#include "fs/TextFile.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(FILE *fp, const Directory &directory)
+{
+ if (!directory.IsRoot()) {
+ const char *type = DeviceToTypeString(directory.device);
+ if (type != nullptr)
+ fprintf(fp, DIRECTORY_TYPE "%s\n", type);
+
+ if (directory.mtime != 0)
+ fprintf(fp, DIRECTORY_MTIME "%lu\n",
+ (unsigned long)directory.mtime);
+
+ fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory.GetPath());
+ }
+
+ for (const auto &child : directory.children) {
+ fprintf(fp, DIRECTORY_DIR "%s\n", child.GetName());
+
+ if (!child.IsMount())
+ directory_save(fp, child);
+
+ if (ferror(fp))
+ return;
+ }
+
+ for (const auto &song : directory.songs)
+ song_save(fp, song);
+
+ playlist_vector_save(fp, directory.playlists);
+
+ if (!directory.IsRoot())
+ fprintf(fp, 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..07e9e158b
--- /dev/null
+++ b/src/db/plugins/simple/DirectorySave.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_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/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..97d3e52df
--- /dev/null
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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/TextFile.hxx"
+#include "config/ConfigData.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/CharUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <errno.h>
+
+static constexpr Domain simple_db_domain("simple_db");
+
+inline SimpleDatabase::SimpleDatabase()
+ :Database(simple_db_plugin),
+ path(AllocatedPath::Null()),
+ cache_path(AllocatedPath::Null()),
+ prefixed_light_song(nullptr) {}
+
+inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path)
+ :Database(simple_db_plugin),
+ path(std::move(_path)),
+ path_utf8(path.ToUTF8()),
+ 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;
+
+ 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);
+ 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)
+{
+ 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");
+
+ 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;
+}
+
+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, '_');
+
+ auto db = new SimpleDatabase(AllocatedPath::Build(cache_path,
+ name.c_str()));
+ 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..e27b3d956
--- /dev/null
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.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_SIMPLE_DATABASE_PLUGIN_HXX
+#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
+
+#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;
+
+ /**
+ * 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);
+
+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;
+
+ 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 {
+ 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..3bd3d8316
--- /dev/null
+++ b/src/db/plugins/simple/Song.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 "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_ms(0), end_ms(0)
+{
+ 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_ms = other.GetStartMS();
+ song->end_ms = other.GetEndMS();
+ 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_ms = start_ms;
+ dest.end_ms = end_ms;
+ return dest;
+}
diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx
new file mode 100644
index 000000000..b2e85aa6b
--- /dev/null
+++ b/src/db/plugins/simple/Song.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_SONG_HXX
+#define MPD_SONG_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 in milliseconds.
+ */
+ unsigned start_ms;
+
+ /**
+ * End of this sub-song within the file in milliseconds.
+ * Unused if zero.
+ */
+ unsigned end_ms;
+
+ /**
+ * 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..c097f7644
--- /dev/null
+++ b/src/db/plugins/upnp/ContentDirectoryService.cxx
@@ -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.
+ */
+
+#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/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..e43cd48a6
--- /dev/null
+++ b/src/db/plugins/upnp/Directory.cxx
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 int
+ParseDuration(const char *duration)
+{
+ char *endptr;
+
+ unsigned result = ParseUnsigned(duration, &endptr);
+ if (endptr == duration || *endptr != ':')
+ return 0;
+
+ result *= 60;
+ duration = endptr + 1;
+ result += ParseUnsigned(duration, &endptr);
+ if (endptr == duration || *endptr != ':')
+ return 0;
+
+ result *= 60;
+ duration = endptr + 1;
+ result += ParseUnsigned(duration, &endptr);
+ if (endptr == duration || *endptr != 0)
+ return 0;
+
+ return 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)
+ {
+ }
+
+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.SetTime(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..66951f402
--- /dev/null
+++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx
@@ -0,0 +1,786 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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_ms = end_ms = 0;
+ }
+};
+
+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_ms = song.end_ms = 0;
+
+ 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.song_count = 0;
+ stats.total_duration = 0;
+ stats.artist_count = 0;
+ stats.album_count = 0;
+ 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..fc08c2659
--- /dev/null
+++ b/src/db/update/Editor.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_UPDATE_DATABASE_HXX
+#define MPD_UPDATE_DATABASE_HXX
+
+#include "check.h"
+#include "Remove.hxx"
+
+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..c2783690e
--- /dev/null
+++ b/src/db/update/InotifySource.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 "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/db/update/InotifySource.hxx b/src/db/update/InotifySource.hxx
new file mode 100644
index 000000000..77c11093c
--- /dev/null
+++ b/src/db/update/InotifySource.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_INOTIFY_SOURCE_HXX
+#define MPD_INOTIFY_SOURCE_HXX
+
+#include "event/SocketMonitor.hxx"
+#include "util/FifoBuffer.hxx"
+
+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:
+ ~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..f0457efa1
--- /dev/null
+++ b/src/db/update/Remove.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_UPDATE_REMOVE_HXX
+#define MPD_UPDATE_REMOVE_HXX
+
+#include "check.h"
+#include "event/DeferredMonitor.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+
+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..cbb4a3f9d
--- /dev/null
+++ b/src/db/update/Service.hxx
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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"
+
+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..fa19a8b5a
--- /dev/null
+++ b/src/db/update/UpdateIO.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.
+ */
+
+#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 "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..cce276ab0
--- /dev/null
+++ b/src/db/update/Walk.hxx
@@ -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.
+ */
+
+#ifndef MPD_UPDATE_WALK_HXX
+#define MPD_UPDATE_WALK_HXX
+
+#include "check.h"
+#include "Editor.hxx"
+
+#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 3fb23bc20..000000000
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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 wil 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 {
- 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..5c4822804
--- /dev/null
+++ b/src/decoder/DecoderAPI.cxx
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "Log.hxx"
+
+#include <assert.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(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 = total_time;
+
+ 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_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);
+}
+
+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)
+{
+ struct music_chunk *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;
+
+ /* 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;
+ data = decoder.convert->Convert(data, length,
+ &length,
+ error);
+ if (data == nullptr) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ LogError(error);
+ return DecoderCommand::STOP;
+ }
+ } else {
+ assert(dc.in_audio_format == dc.out_audio_format);
+ }
+
+ while (length > 0) {
+ struct music_chunk *chunk;
+ bool full;
+
+ chunk = decoder.GetChunk();
+ if (chunk == nullptr) {
+ assert(dc.command != DecoderCommand::NONE);
+ return dc.command;
+ }
+
+ const auto dest =
+ chunk->Write(dc.out_audio_format,
+ decoder.timestamp -
+ dc.song->GetStartMS() / 1000.0,
+ kbit_rate);
+ if (dest.IsNull()) {
+ /* the chunk is full, flush it */
+ decoder.FlushChunk();
+ 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.FlushChunk();
+ }
+
+ 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.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..c57a02e01
--- /dev/null
+++ b/src/decoder/DecoderAPI.hxx
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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"
+
+// IWYU pragma: end_exports
+
+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 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);
+
+/**
+ * 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..47671513e
--- /dev/null
+++ b/src/decoder/DecoderBuffer.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "DecoderBuffer.hxx"
+#include "DecoderAPI.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/VarSize.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.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 *_decoder, InputStream &_is,
+ size_t _size)
+ :decoder(_decoder), is(&_is),
+ size(_size), length(0), consumed(0) {}
+};
+
+DecoderBuffer *
+decoder_buffer_new(Decoder *decoder, InputStream &is,
+ size_t size)
+{
+ assert(size > 0);
+
+ return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data),
+ size,
+ decoder, is, size);
+}
+
+void
+decoder_buffer_free(DecoderBuffer *buffer)
+{
+ assert(buffer != nullptr);
+
+ DeleteVarSize(buffer);
+}
+
+bool
+decoder_buffer_is_empty(const DecoderBuffer *buffer)
+{
+ return buffer->consumed == buffer->length;
+}
+
+bool
+decoder_buffer_is_full(const DecoderBuffer *buffer)
+{
+ return buffer->consumed == 0 && buffer->length == buffer->size;
+}
+
+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;
+}
+
+ConstBuffer<void>
+decoder_buffer_read(const DecoderBuffer *buffer)
+{
+ return {
+ buffer->data + buffer->consumed,
+ buffer->length - 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)
+{
+ bool success;
+
+ /* this could probably be optimized by seeking */
+
+ while (true) {
+ auto data = decoder_buffer_read(buffer);
+ if (!data.IsEmpty()) {
+ if (data.size > nbytes)
+ data.size = nbytes;
+ decoder_buffer_consume(buffer, data.size);
+ nbytes -= data.size;
+ if (nbytes == 0)
+ return true;
+ }
+
+ success = decoder_buffer_fill(buffer);
+ if (!success)
+ return false;
+ }
+}
diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx
new file mode 100644
index 000000000..d6f303c36
--- /dev/null
+++ b/src/decoder/DecoderBuffer.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_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;
+class InputStream;
+
+template<typename T> struct ConstBuffer;
+
+/**
+ * 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
+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);
+
+/**
+ * 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
+ */
+gcc_pure
+ConstBuffer<void>
+decoder_buffer_read(const DecoderBuffer *buffer);
+
+/**
+ * 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/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..d78fc66c9
--- /dev/null
+++ b/src/decoder/DecoderControl.cxx
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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,
+ unsigned _start_ms, unsigned _end_ms,
+ MusicBuffer &_buffer, MusicPipe &_pipe)
+{
+ assert(_song != nullptr);
+ assert(_pipe.IsEmpty());
+
+ delete song;
+ 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/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx
new file mode 100644
index 000000000..4e5c43b5a
--- /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 "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;
+ 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.
+ */
+ DetachedSong *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 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_ms see #DecoderControl
+ * @param end_ms see #DecoderControl
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
+ void Start(DetachedSong *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/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..b50fee185
--- /dev/null
+++ b/src/decoder/DecoderInternal.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 "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;
+}
+
+struct music_chunk *
+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(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..bef6f6c13
--- /dev/null
+++ b/src/decoder/DecoderInternal.hxx
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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 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_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;
+
+ /**
+ * 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
+ */
+ music_chunk *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..5d9d44d34
--- /dev/null
+++ b/src/decoder/DecoderList.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"
+#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
+ &dsdiff_decoder_plugin,
+ &dsf_decoder_plugin,
+#ifdef HAVE_FAAD
+ &faad_decoder_plugin,
+#endif
+#ifdef HAVE_MPCDEC
+ &mpcdec_decoder_plugin,
+#endif
+#ifdef HAVE_WAVPACK
+ &wavpack_decoder_plugin,
+#endif
+#ifdef HAVE_MODPLUG
+ &modplug_decoder_plugin,
+#endif
+#ifdef ENABLE_MIKMOD_DECODER
+ &mikmod_decoder_plugin,
+#endif
+#ifdef ENABLE_SIDPLAY
+ &sidplay_decoder_plugin,
+#endif
+#ifdef ENABLE_WILDMIDI
+ &wildmidi_decoder_plugin,
+#endif
+#ifdef ENABLE_FLUIDSYNTH
+ &fluidsynth_decoder_plugin,
+#endif
+#ifdef HAVE_ADPLUG
+ &adplug_decoder_plugin,
+#endif
+#ifdef HAVE_FFMPEG
+ &ffmpeg_decoder_plugin,
+#endif
+#ifdef HAVE_GME
+ &gme_decoder_plugin,
+#endif
+ &pcm_decoder_plugin,
+ 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..3be812c3b
--- /dev/null
+++ b/src/decoder/DecoderPlugin.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 "DecoderPlugin.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+bool
+DecoderPlugin::SupportsSuffix(const char *suffix) const
+{
+ assert(suffix != nullptr);
+
+ return suffixes != nullptr && string_array_contains(suffixes, suffix);
+
+}
+
+bool
+DecoderPlugin::SupportsMimeType(const char *mime_type) const
+{
+ assert(mime_type != nullptr);
+
+ 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..06735de83
--- /dev/null
+++ b/src/decoder/DecoderThread.cxx
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "DecoderAPI.hxx"
+#include "input/InputStream.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 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)
+{
+ const char *const suffix = uri_get_suffix(uri);
+
+ 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.c_str());
+ 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_ms > 0,
+ 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;
+
+ /* fall through */
+
+ case DecoderCommand::SEEK:
+ 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 f8506851a..000000000
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ /dev/null
@@ -1,525 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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;
-
-#ifdef HAVE_ID3TAG
- metadata->id3_size = 0;
-#endif
-
- /* Now process all the remaining chunk headers in the stream
- and record their position and size */
-
- const auto size = is.GetSize();
- while (is.GetOffset() < size) {
- 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 (chunk_size != 0) {
- if (!dsdlib_skip(decoder, is, chunk_size))
- break;
- }
-
- if (is.GetOffset() < size) {
- if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
- return false;
- }
- }
- /* 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 > 0) {
- /* 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 ad5483c32..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 > 0) {
- /* 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 9fd20167d..000000000
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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>
-
-#define AAC_MAX_CHANNELS 6
-
-static const unsigned adts_sample_rates[] =
- { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
- 16000, 12000, 11025, 8000, 7350, 0, 0, 0
-};
-
-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)
-{
- size_t length, frame_length;
- bool ret;
-
- while (true) {
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr || length < 8) {
- /* not enough data yet */
- ret = decoder_buffer_fill(buffer);
- if (!ret)
- /* failed */
- return 0;
-
- continue;
- }
-
- /* find the 0xff marker */
- const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
- if (p == nullptr) {
- /* no marker - discard the buffer */
- decoder_buffer_consume(buffer, length);
- continue;
- }
-
- if (p > data) {
- /* discard data before 0xff */
- decoder_buffer_consume(buffer, p - data);
- continue;
- }
-
- /* is it a frame? */
- frame_length = adts_check_frame(data);
- if (frame_length == 0) {
- /* it's just some random 0xff byte; discard it
- and continue searching */
- decoder_buffer_consume(buffer, 1);
- continue;
- }
-
- if (length < frame_length) {
- /* available buffer size is smaller than the
- frame will be - attempt to read more
- data */
- ret = decoder_buffer_fill(buffer);
- if (!ret) {
- /* not enough data; discard this frame
- to prevent a possible buffer
- overflow */
- data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data != nullptr)
- decoder_buffer_consume(buffer, length);
- }
-
- continue;
- }
-
- /* found a full frame! */
- return frame_length;
- }
-}
-
-static float
-adts_song_duration(DecoderBuffer *buffer)
-{
- unsigned int frames, frame_length;
- unsigned sample_rate = 0;
- float frames_per_second;
-
- /* Read all frames to ensure correct time and bitrate */
- for (frames = 0;; frames++) {
- frame_length = adts_find_frame(buffer);
- if (frame_length == 0)
- break;
-
-
- if (frames == 0) {
- size_t buffer_length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &buffer_length);
- assert(data != nullptr);
- assert(frame_length <= buffer_length);
-
- sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
- }
-
- decoder_buffer_consume(buffer, frame_length);
- }
-
- frames_per_second = (float)sample_rate / 1024.0;
- if (frames_per_second <= 0)
- return -1;
-
- return (float)frames / frames_per_second;
-}
-
-static float
-faad_song_duration(DecoderBuffer *buffer, InputStream &is)
-{
- size_t fileread;
- size_t tagsize;
- size_t length;
- bool success;
-
- const auto size = is.GetSize();
- fileread = size >= 0 ? size : 0;
-
- decoder_buffer_fill(buffer);
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr)
- return -1;
-
- tagsize = 0;
- if (length >= 10 && !memcmp(data, "ID3", 3)) {
- /* skip the ID3 tag */
-
- tagsize = (data[6] << 21) | (data[7] << 14) |
- (data[8] << 7) | (data[9] << 0);
-
- tagsize += 10;
-
- success = decoder_buffer_skip(buffer, tagsize) &&
- decoder_buffer_fill(buffer);
- if (!success)
- return -1;
-
- data = (const uint8_t *)decoder_buffer_read(buffer, &length);
- if (data == nullptr)
- return -1;
- }
-
- if (is.IsSeekable() && length >= 2 &&
- data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
- /* obtain the duration from the ADTS header */
- float song_length = adts_song_duration(buffer);
-
- is.LockSeek(tagsize, SEEK_SET, IgnoreError());
-
- data = (const uint8_t *)decoder_buffer_read(buffer, &length);
- if (data != nullptr)
- decoder_buffer_consume(buffer, length);
- decoder_buffer_fill(buffer);
-
- return song_length;
- } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
- /* obtain the duration from the ADIF header */
- unsigned bit_rate;
- size_t skip_size = (data[4] & 0x80) ? 9 : 0;
-
- if (8 + skip_size > length)
- /* not enough data yet; skip parsing this
- header */
- return -1;
-
- bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
- (data[5 + skip_size] << 11) |
- (data[6 + skip_size] << 3) |
- (data[7 + skip_size] & 0xE0);
-
- if (fileread != 0 && bit_rate != 0)
- return fileread * 8.0 / bit_rate;
- else
- return fileread;
- } else
- return -1;
-}
-
-/**
- * Wrapper for NeAACDecInit() which works around some API
- * inconsistencies in libfaad.
- */
-static bool
-faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
- AudioFormat &audio_format, Error &error)
-{
- int32_t nbytes;
- uint32_t sample_rate;
- uint8_t channels;
-#ifdef HAVE_FAAD_LONG
- /* neaacdec.h declares all arguments as "unsigned long", but
- internally expects uint32_t pointers. To avoid gcc
- warnings, use this workaround. */
- unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
-#else
- uint32_t *sample_rate_p = &sample_rate;
-#endif
-
- size_t length;
- const unsigned char *data = (const unsigned char *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr) {
- error.Set(faad_decoder_domain, "Empty file");
- return false;
- }
-
- nbytes = NeAACDecInit(decoder,
- /* deconst hack, libfaad requires this */
- const_cast<unsigned char *>(data),
- length,
- sample_rate_p, &channels);
- 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 *buffer;
- float length;
-
- buffer = decoder_buffer_new(nullptr, is,
- FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- length = faad_song_duration(buffer, is);
-
- if (length < 0) {
- bool ret;
- AudioFormat audio_format;
-
- NeAACDecHandle decoder = NeAACDecOpen();
-
- NeAACDecConfigurationPtr config =
- NeAACDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
- NeAACDecSetConfiguration(decoder, config);
-
- decoder_buffer_fill(buffer);
-
- ret = faad_decoder_init(decoder, buffer, audio_format,
- IgnoreError());
- if (ret)
- length = 0;
-
- NeAACDecClose(decoder);
- }
-
- decoder_buffer_free(buffer);
-
- return length;
-}
-
-/**
- * Get a song file's total playing time in seconds, as an int.
- * Returns 0 if the duration is unknown, and a negative value if the
- * file is invalid.
- */
-static int
-faad_get_file_time(InputStream &is)
-{
- int file_time = -1;
- float length;
-
- if ((length = faad_get_file_time_float(is)) >= 0)
- file_time = length + 0.5;
-
- return file_time;
-}
-
-static void
-faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
-{
- float total_time = 0;
- AudioFormat audio_format;
- bool ret;
- uint16_t bit_rate = 0;
- DecoderBuffer *buffer;
-
- buffer = decoder_buffer_new(&mpd_decoder, is,
- FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
- total_time = faad_song_duration(buffer, is);
-
- /* create the libfaad decoder */
-
- NeAACDecHandle decoder = NeAACDecOpen();
-
- NeAACDecConfigurationPtr config =
- NeAACDecGetCurrentConfiguration(decoder);
- config->outputFormat = FAAD_FMT_16BIT;
- config->downMatrix = 1;
- config->dontUpSampleImplicitSBR = 0;
- NeAACDecSetConfiguration(decoder, config);
-
- while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
- decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
- adts_find_frame(buffer);
- decoder_buffer_fill(buffer);
- }
-
- /* initialize it */
-
- Error error;
- ret = faad_decoder_init(decoder, buffer, audio_format, error);
- if (!ret) {
- 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;
- do {
- size_t frame_size;
- const void *decoded;
- NeAACDecFrameInfo frame_info;
-
- /* find the next frame */
-
- frame_size = adts_find_frame(buffer);
- if (frame_size == 0)
- /* end of file */
- break;
-
- /* decode it */
-
- decoded = faad_decoder_decode(decoder, buffer, &frame_info);
-
- if (frame_info.error > 0) {
- 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 bcb1ae3c9..000000000
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ /dev/null
@@ -1,709 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public 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)
- decoder_timestamp(decoder,
- time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
- 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;
- avpd.buf = buffer;
- avpd.buf_size = nbytes;
- avpd.filename = is.uri.c_str();
-
- 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 (codec_context->codec_name[0] != 0)
- FormatDebug(ffmpeg_domain, "codec '%s'",
- codec_context->codec_name);
-
- 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", "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/ac3",
- "audio/aiff"
- "audio/amr",
- "audio/basic",
- "audio/flac",
- "audio/m4a",
- "audio/mp4",
- "audio/mpeg",
- "audio/musepack",
- "audio/ogg",
- "audio/qcelp",
- "audio/vorbis",
- "audio/vorbis+ogg",
- "audio/x-8svx",
- "audio/x-16sv",
- "audio/x-aac",
- "audio/x-ac3",
- "audio/x-aiff"
- "audio/x-alaw",
- "audio/x-au",
- "audio/x-dca",
- "audio/x-eac3",
- "audio/x-flac",
- "audio/x-gsm",
- "audio/x-mace",
- "audio/x-matroska",
- "audio/x-monkeys-audio",
- "audio/x-mpeg",
- "audio/x-ms-wma",
- "audio/x-ms-wax",
- "audio/x-musepack",
- "audio/x-ogg",
- "audio/x-vorbis",
- "audio/x-vorbis+ogg",
- "audio/x-pn-realaudio",
- "audio/x-pn-multirate-realaudio",
- "audio/x-speex",
- "audio/x-tta"
- "audio/x-voc",
- "audio/x-wav",
- "audio/x-wma",
- "audio/x-wv",
- "video/anim",
- "video/quicktime",
- "video/msvideo",
- "video/ogg",
- "video/theora",
- "video/webm",
- "video/x-dv",
- "video/x-flv",
- "video/x-matroska",
- "video/x-mjpeg",
- "video/x-mpeg",
- "video/x-ms-asf",
- "video/x-msvideo",
- "video/x-ms-wmv",
- "video/x-ms-wvx",
- "video/x-ms-wm",
- "video/x-ms-wmx",
- "video/x-nut",
- "video/x-pva",
- "video/x-theora",
- "video/x-vid",
- "video/x-wmv",
- "video/x-xvid",
-
- /* special value for the "ffmpeg" input plugin: all streams by
- the "ffmpeg" input plugin shall be decoded by this
- plugin */
- "audio/x-mpd-ffmpeg",
-
- 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 d67ee4b42..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 / 100);
-
- 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 77b132962..000000000
--- a/src/decoder/SndfileDecoderPlugin.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 "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");
-
-static sf_count_t
-sndfile_vio_get_filelen(void *user_data)
-{
- const InputStream &is = *(const InputStream *)user_data;
-
- return is.GetSize();
-}
-
-static sf_count_t
-sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
-{
- InputStream &is = *(InputStream *)user_data;
-
- if (!is.LockSeek(offset, whence, IgnoreError()))
- return -1;
-
- return is.GetOffset();
-}
-
-static sf_count_t
-sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
-{
- InputStream &is = *(InputStream *)user_data;
-
- sf_count_t total_bytes = 0;
- Error error;
-
- /* this loop is necessary because libsndfile chokes on partial
- reads */
-
- do {
- size_t nbytes = is.LockRead((char *)ptr + total_bytes,
- count - total_bytes, error);
- if (nbytes == 0) {
- if (error.IsDefined()) {
- LogError(error);
- return -1;
- }
-
- break;
- }
-
- total_bytes += nbytes;
- } while (total_bytes < count);
-
- return total_bytes;
-}
-
-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)
-{
- const InputStream &is = *(const InputStream *)user_data;
-
- 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;
-
- sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
- 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..32a2432f4
--- /dev/null
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.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 "AdPlugDecoderPlugin.h"
+#include "tag/TagHandler.hxx"
+#include "../DecoderAPI.hxx"
+#include "CheckAudioFormat.hxx"
+#include "fs/Path.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, 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,
+ 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(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,
+ 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/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..994a5a109
--- /dev/null
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.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 "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>
+
+/* 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 wil abort playback; therefore always force full
+ reads */
+ return decoder_read_full(decoder, is, buffer, size)
+ ? size
+ : 0;
+ }
+};
+
+gcc_pure
+static int
+audiofile_get_duration(Path path_fs)
+{
+ int total_time;
+ AFfilehandle af_fp = afOpenFile(path_fs.c_str(), "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;
+
+ InputStream::offset_type offset = _offset;
+ if (is_relative)
+ offset += is.GetOffset();
+
+ Error error;
+ if (is.LockSeek(offset, IgnoreError())) {
+ return is.GetOffset();
+ } else {
+ 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(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ int total_time = audiofile_get_duration(path_fs);
+
+ if (total_time < 0) {
+ FormatWarning(audiofile_domain,
+ "Failed to get total song time from: %s",
+ path_fs.c_str());
+ 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/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..0f10b20e9
--- /dev/null
+++ b/src/decoder/plugins/DsdLib.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.
+ */
+
+/* \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 <string.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,
+ uint64_t offset)
+{
+ if (is.IsSeekable())
+ return is.Seek(offset, IgnoreError());
+
+ if (uint64_t(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,
+ uint64_t delta)
+{
+ if (delta == 0)
+ return true;
+
+ if (is.IsSeekable())
+ return is.Seek(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);
+}
+
+#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/plugins/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx
new file mode 100644
index 000000000..5250922ac
--- /dev/null
+++ b/src/decoder/plugins/DsdLib.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_DECODER_DSDLIB_HXX
+#define MPD_DECODER_DSDLIB_HXX
+
+#include "system/ByteOrder.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,
+ uint64_t offset);
+
+bool
+dsdlib_skip(Decoder *decoder, InputStream &is,
+ uint64_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/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
new file mode 100644
index 000000000..d6f402911
--- /dev/null
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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;
+ 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;
+
+#ifdef HAVE_ID3TAG
+ metadata->id3_size = 0;
+#endif
+
+ /* Now process all the remaining chunk headers in the stream
+ and record their position and size */
+
+ const auto size = is.GetSize();
+ while (is.GetOffset() < size) {
+ 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 (chunk_size != 0) {
+ if (!dsdlib_skip(decoder, is, chunk_size))
+ break;
+ }
+
+ if (is.GetOffset() < size) {
+ if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
+ return false;
+ }
+ }
+ /* 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 > 0) {
+ /* 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/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..6a7267f61
--- /dev/null
+++ b/src/decoder/plugins/DsfDecoderPlugin.cxx
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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"
+
+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 > 0) {
+ /* 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/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..c7f72da15
--- /dev/null
+++ b/src/decoder/plugins/FaadDecoderPlugin.cxx
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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>
+
+#define AAC_MAX_CHANNELS 6
+
+static const unsigned adts_sample_rates[] =
+ { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+ 16000, 12000, 11025, 8000, 7350, 0, 0, 0
+};
+
+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(decoder_buffer_read(buffer));
+ if (data.size < 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.data, 0xff, data.size);
+ if (p == nullptr) {
+ /* no marker - discard the buffer */
+ decoder_buffer_clear(buffer);
+ continue;
+ }
+
+ if (p > data.data) {
+ /* discard data before 0xff */
+ decoder_buffer_consume(buffer, p - data.data);
+ continue;
+ }
+
+ /* is it a frame? */
+ 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 */
+ decoder_buffer_consume(buffer, 1);
+ continue;
+ }
+
+ if (data.size < 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)
+{
+ unsigned sample_rate = 0;
+
+ /* Read all frames to ensure correct time and bitrate */
+ unsigned frames = 0;
+ for (;; frames++) {
+ unsigned frame_length = adts_find_frame(buffer);
+ if (frame_length == 0)
+ break;
+
+ if (frames == 0) {
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ assert(!data.IsEmpty());
+ assert(frame_length <= data.size);
+
+ sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2];
+ }
+
+ decoder_buffer_consume(buffer, frame_length);
+ }
+
+ float frames_per_second = (float)sample_rate / 1024.0;
+ if (frames_per_second <= 0)
+ return -1;
+
+ return (float)frames / frames_per_second;
+}
+
+static float
+faad_song_duration(DecoderBuffer *buffer, InputStream &is)
+{
+ const auto size = is.GetSize();
+ const size_t fileread = size >= 0 ? size : 0;
+
+ decoder_buffer_fill(buffer);
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty())
+ return -1;
+
+ 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;
+
+ bool success = decoder_buffer_skip(buffer, tagsize) &&
+ decoder_buffer_fill(buffer);
+ if (!success)
+ return -1;
+
+ data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty())
+ return -1;
+ }
+
+ if (is.IsSeekable() && data.size >= 2 &&
+ data.data[0] == 0xFF && ((data.data[1] & 0xF6) == 0xF0)) {
+ /* obtain the duration from the ADTS header */
+ float song_length = adts_song_duration(buffer);
+
+ is.LockSeek(tagsize, IgnoreError());
+
+ decoder_buffer_clear(buffer);
+ decoder_buffer_fill(buffer);
+
+ return song_length;
+ } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) {
+ /* obtain the duration from the ADIF header */
+ unsigned bit_rate;
+ size_t skip_size = (data.data[4] & 0x80) ? 9 : 0;
+
+ if (8 + skip_size > data.size)
+ /* not enough data yet; skip parsing this
+ header */
+ return -1;
+
+ bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) |
+ (data.data[5 + skip_size] << 11) |
+ (data.data[6 + skip_size] << 3) |
+ (data.data[7 + skip_size] & 0xE0);
+
+ if (fileread != 0 && bit_rate != 0)
+ return fileread * 8.0 / bit_rate;
+ else
+ return fileread;
+ } else
+ return -1;
+}
+
+/**
+ * Wrapper for NeAACDecInit() which works around some API
+ * inconsistencies in libfaad.
+ */
+static bool
+faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
+ AudioFormat &audio_format, Error &error)
+{
+ uint32_t sample_rate;
+#ifdef HAVE_FAAD_LONG
+ /* neaacdec.h declares all arguments as "unsigned long", but
+ internally expects uint32_t pointers. To avoid gcc
+ warnings, use this workaround. */
+ unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
+#else
+ uint32_t *sample_rate_p = &sample_rate;
+#endif
+
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty()) {
+ error.Set(faad_decoder_domain, "Empty file");
+ return false;
+ }
+
+ uint8_t channels;
+ int32_t nbytes = NeAACDecInit(decoder,
+ /* deconst hack, libfaad requires this */
+ const_cast<uint8_t *>(data.data),
+ data.size,
+ sample_rate_p, &channels);
+ if (nbytes < 0) {
+ error.Set(faad_decoder_domain, "Not an AAC stream");
+ return false;
+ }
+
+ 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)
+{
+ auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
+ if (data.IsEmpty())
+ return nullptr;
+
+ return NeAACDecDecode(decoder, frame_info,
+ /* deconst hack, libfaad requires this */
+ const_cast<uint8_t *>(data.data),
+ data.size);
+}
+
+/**
+ * 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 *buffer =
+ decoder_buffer_new(nullptr, is,
+ FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
+ float length = faad_song_duration(buffer, is);
+
+ if (length < 0) {
+ NeAACDecHandle decoder = NeAACDecOpen();
+
+ NeAACDecConfigurationPtr config =
+ NeAACDecGetCurrentConfiguration(decoder);
+ config->outputFormat = FAAD_FMT_16BIT;
+ NeAACDecSetConfiguration(decoder, config);
+
+ decoder_buffer_fill(buffer);
+
+ AudioFormat audio_format;
+ 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)
+{
+ int file_time = -1;
+ float length;
+
+ if ((length = faad_get_file_time_float(is)) >= 0)
+ file_time = length + 0.5;
+
+ return file_time;
+}
+
+static void
+faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
+{
+ DecoderBuffer *buffer =
+ decoder_buffer_new(&mpd_decoder, is,
+ FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
+ const float total_time = faad_song_duration(buffer, is);
+
+ /* create the libfaad decoder */
+
+ NeAACDecHandle decoder = NeAACDecOpen();
+
+ NeAACDecConfigurationPtr config =
+ NeAACDecGetCurrentConfiguration(decoder);
+ config->outputFormat = FAAD_FMT_16BIT;
+ config->downMatrix = 1;
+ config->dontUpSampleImplicitSBR = 0;
+ NeAACDecSetConfiguration(decoder, config);
+
+ while (!decoder_buffer_is_full(buffer) && !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;
+ unsigned bit_rate = 0;
+ do {
+ size_t frame_size;
+ const void *decoded;
+ NeAACDecFrameInfo frame_info;
+
+ /* find the next frame */
+
+ frame_size = adts_find_frame(buffer);
+ if (frame_size == 0)
+ /* end of file */
+ break;
+
+ /* decode it */
+
+ decoded = faad_decoder_decode(decoder, buffer, &frame_info);
+
+ if (frame_info.error > 0) {
+ 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 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..9139a622b
--- /dev/null
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 "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>
+
+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;
+
+ 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:
+ 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;
+}
+
+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)
+ decoder_timestamp(decoder,
+ time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
+ 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;
+ avpd.buf = buffer;
+ avpd.buf_size = nbytes;
+ avpd.filename = is.GetURI();
+
+ 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 (codec_context->codec_name[0] != 0)
+ FormatDebug(ffmpeg_domain, "codec '%s'",
+ codec_context->codec_name);
+
+ 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;
+ }
+
+ int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
+ ? format_context->duration / AV_TIME_BASE
+ : 0;
+
+ 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_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.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)
+ 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", "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/ac3",
+ "audio/aiff"
+ "audio/amr",
+ "audio/basic",
+ "audio/flac",
+ "audio/m4a",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/musepack",
+ "audio/ogg",
+ "audio/qcelp",
+ "audio/vorbis",
+ "audio/vorbis+ogg",
+ "audio/x-8svx",
+ "audio/x-16sv",
+ "audio/x-aac",
+ "audio/x-ac3",
+ "audio/x-aiff"
+ "audio/x-alaw",
+ "audio/x-au",
+ "audio/x-dca",
+ "audio/x-eac3",
+ "audio/x-flac",
+ "audio/x-gsm",
+ "audio/x-mace",
+ "audio/x-matroska",
+ "audio/x-monkeys-audio",
+ "audio/x-mpeg",
+ "audio/x-ms-wma",
+ "audio/x-ms-wax",
+ "audio/x-musepack",
+ "audio/x-ogg",
+ "audio/x-vorbis",
+ "audio/x-vorbis+ogg",
+ "audio/x-pn-realaudio",
+ "audio/x-pn-multirate-realaudio",
+ "audio/x-speex",
+ "audio/x-tta"
+ "audio/x-voc",
+ "audio/x-wav",
+ "audio/x-wma",
+ "audio/x-wv",
+ "video/anim",
+ "video/quicktime",
+ "video/msvideo",
+ "video/ogg",
+ "video/theora",
+ "video/webm",
+ "video/x-dv",
+ "video/x-flv",
+ "video/x-matroska",
+ "video/x-mjpeg",
+ "video/x-mpeg",
+ "video/x-ms-asf",
+ "video/x-msvideo",
+ "video/x-ms-wmv",
+ "video/x-ms-wvx",
+ "video/x-ms-wm",
+ "video/x-ms-wmx",
+ "video/x-nut",
+ "video/x-pva",
+ "video/x-theora",
+ "video/x-vid",
+ "video/x-wmv",
+ "video/x-xvid",
+
+ /* special value for the "ffmpeg" input plugin: all streams by
+ the "ffmpeg" input plugin shall be decoded by this
+ plugin */
+ "audio/x-mpd-ffmpeg",
+
+ 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..f06a52cf8
--- /dev/null
+++ b/src/decoder/plugins/FlacCommon.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.
+ */
+
+/*
+ * 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))
+ decoder_replay_gain(data->decoder, &rgi);
+
+ decoder_mixramp(data->decoder, flac_parse_mixramp(block));
+
+ 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();
+
+ decoder_initialized(data->decoder, data->audio_format,
+ data->input_stream.IsSeekable(),
+ (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/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..39a0fce73
--- /dev/null
+++ b/src/decoder/plugins/FlacDecoderPlugin.cxx
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with 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 */
+ decoder_initialized(data->decoder, data->audio_format,
+ data->input_stream.IsSeekable(),
+ (float)data->total_frames /
+ (float)data->audio_format.sample_rate);
+ 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(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(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..d37cea532
--- /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;
+
+ InputStream::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..b921e8481
--- /dev/null
+++ b/src/decoder/plugins/FlacMetadata.cxx
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "ReplayGainInfo.hxx"
+#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
+
+#include <string.h>
+
+static const char *
+vorbis_comment_value(const FLAC__StreamMetadata *block,
+ const char *name)
+{
+ int offset =
+ FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
+ name);
+ if (offset < 0)
+ return nullptr;
+
+ size_t name_length = strlen(name);
+
+ const FLAC__StreamMetadata_VorbisComment_Entry &vc =
+ block->data.vorbis_comment.comments[offset];
+ const char *comment = (const char *)vc.entry;
+
+ /* 1 is for '=' */
+ return comment + name_length + 1;
+}
+
+static bool
+flac_find_float_comment(const FLAC__StreamMetadata *block,
+ const char *cmnt, float *fl)
+{
+ const char *value = vorbis_comment_value(block, cmnt);
+ if (value == nullptr)
+ return false;
+
+ *fl = (float)atof(value);
+ 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)
+{
+ const char *value = vorbis_comment_value(block, cmnt);
+ if (value == nullptr)
+ return std::string();
+
+ return std::string(value);
+}
+
+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;
+ */
+static const char *
+flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name)
+{
+ size_t name_length = strlen(name);
+ const char *comment = (const char*)entry->entry;
+
+ if (!StringEqualsCaseASCII(comment, name, name_length))
+ return nullptr;
+
+ if (comment[name_length] == '=') {
+ 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 = 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);
+}
+
+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..e0449b2a2
--- /dev/null
+++ b/src/decoder/plugins/FlacMetadata.hxx
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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;
+
+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);
+
+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..bdf30baea
--- /dev/null
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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, -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(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..469da2540
--- /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 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(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,
+ ti->length / 100);
+
+ 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..886aa1795
--- /dev/null
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "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>
+
+#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, 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 = 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;
+ }
+
+ 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 */
+
+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 (!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 */
+ 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/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/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..a1938617d
--- /dev/null
+++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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, 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(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..336870817
--- /dev/null
+++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 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/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..f86cb3c81
--- /dev/null
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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;
+
+ 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/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..76702a08f
--- /dev/null
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with 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 "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)
+{
+ 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, Path 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.c_str(), 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(Path 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.c_str(), 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/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..15e9c5c92
--- /dev/null
+++ b/src/decoder/plugins/OggFind.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "OggFind.hxx"
+#include "OggSyncState.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,
+ InputStream::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..8ced7fd86
--- /dev/null
+++ b/src/decoder/plugins/OggFind.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_OGG_FIND_HXX
+#define MPD_OGG_FIND_HXX
+
+#include "check.h"
+#include "input/InputStream.hxx"
+
+#include <ogg/ogg.h>
+
+class OggSyncState;
+
+/**
+ * Skip all pages/packets until an end-of-stream (EOS) packet for the
+ * specified stream is found.
+ *
+ * @return true if the EOS packet was found
+ */
+bool
+OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet);
+
+/**
+ * Seek the #InputStream and find the next Ogg page.
+ */
+bool
+OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
+ InputStream::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..8d1f75e72
--- /dev/null
+++ b/src/decoder/plugins/OpusDecoderPlugin.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" /* 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;
+
+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()
+{
+ 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 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.GetOffset();
+ 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, 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 = new opus_int16[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();
+ 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.IsSeekable());
+ assert(input_stream.KnownSize());
+ assert(input_stream.GetOffset() >= 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.GetSize()
+ / eos_granulepos);
+
+ if (!OggSeekPageAtOffset(oy, os, input_stream, offset))
+ 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/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..3b9c60691
--- /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 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, 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..e3e3b8d96
--- /dev/null
+++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 int
+get_song_length(Path path_fs)
+{
+ if (songlength_database == nullptr)
+ return -1;
+
+ 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 -1;
+ }
+ 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 -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, 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);
+
+ 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(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 */
+ 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(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..cf3aa61d5
--- /dev/null
+++ b/src/decoder/plugins/SndfileDecoderPlugin.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 "SndfileDecoderPlugin.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 <sndfile.h>
+
+static constexpr Domain sndfile_domain("sndfile");
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ const InputStream &is = *(const InputStream *)user_data;
+
+ return is.GetSize();
+}
+
+static sf_count_t
+sndfile_vio_seek(sf_count_t _offset, int whence, void *user_data)
+{
+ InputStream &is = *(InputStream *)user_data;
+
+ InputStream::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;
+ }
+
+ if (!is.LockSeek(offset, IgnoreError()))
+ return -1;
+
+ return is.GetOffset();
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ InputStream &is = *(InputStream *)user_data;
+
+ sf_count_t total_bytes = 0;
+ Error error;
+
+ /* this loop is necessary because libsndfile chokes on partial
+ reads */
+
+ do {
+ size_t nbytes = is.LockRead((char *)ptr + total_bytes,
+ count - total_bytes, error);
+ if (nbytes == 0) {
+ if (error.IsDefined()) {
+ LogError(error);
+ return -1;
+ }
+
+ break;
+ }
+
+ total_bytes += nbytes;
+ } while (total_bytes < count);
+
+ return total_bytes;
+}
+
+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)
+{
+ const InputStream &is = *(const InputStream *)user_data;
+
+ 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;
+
+ sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
+ 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(Path 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.c_str(), 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.c_str());
+ 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/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..2a0820ab5
--- /dev/null
+++ b/src/decoder/plugins/VorbisComments.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 "VorbisComments.hxx"
+#include "XiphTags.hxx"
+#include "tag/TagTable.hxx"
+#include "tag/TagHandler.hxx"
+#include "tag/TagBuilder.hxx"
+#include "ReplayGainInfo.hxx"
+#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
+
+#include <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) {
+ 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..72542e3a2
--- /dev/null
+++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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;
+
+ InputStream::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;
+}
+
+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;
+ }
+
+ 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)
+{
+ VorbisInputStream vis(nullptr, is);
+ OggVorbis_File vf;
+
+ if (!vorbis_is_open(&vis, &vf))
+ 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",
+ 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..2f60090c1
--- /dev/null
+++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 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(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;
+ }
+
+ 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 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)
+{
+ return wpin(id)->is.GetOffset();
+}
+
+static int
+wavpack_input_set_pos_abs(void *id, uint32_t pos)
+{
+ return wpin(id)->is.LockSeek(pos, IgnoreError()) ? 0 : -1;
+}
+
+static int
+wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
+{
+ InputStream &is = wpin(id)->is;
+
+ InputStream::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)
+{
+ 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.KnownSize())
+ return 0;
+
+ return wpin(id)->is.GetSize();
+}
+
+static int
+wavpack_input_can_seek(void *id)
+{
+ return wpin(id)->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..a3a4b2745
--- /dev/null
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.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 "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;
+ }
+
+ 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(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;
+ }
+
+ 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/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..5cb65e4f2
--- /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 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 *)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..00b8eec7c
--- /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) {
+ long written;
+
+ if (flush) {
+ /* fill remaining with 0s */
+ for (; input_pos < frame_size; input_pos++) {
+ stereo[0][input_pos] = stereo[1][input_pos] = 0;
+ }
+ }
+
+ 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;
+ long written;
+
+ /* flush buffers and flush shine */
+ encoder->WriteChunk(true);
+ 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..7fdb3066f
--- /dev/null
+++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 (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/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 92e350e85..c590c215d 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..1c9b44e46 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
@@ -23,13 +23,12 @@
#include "check.h"
#include "SocketMonitor.hxx"
#include "util/FifoBuffer.hxx"
-#include "Compiler.h"
#include <assert.h>
#include <stdint.h>
-struct fifo_buffer;
class Error;
+class EventLoop;
/**
* A #SocketMonitor specialization that adds an input buffer.
diff --git a/src/event/Call.cxx b/src/event/Call.cxx
index ab1d5ffbd..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..3d3ab22b7 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
@@ -23,64 +23,31 @@
#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 +55,6 @@ public:
protected:
virtual void RunDeferred() = 0;
-
-private:
-#ifdef USE_EPOLL
- virtual bool OnSocketReady(unsigned flags) override final;
-#else
- void Run();
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/FullyBufferedSocket.cxx b/src/event/FullyBufferedSocket.cxx
index 8b57b1308..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..65aaa38cf 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,35 @@
#include "check.h"
-#ifndef USE_EPOLL
-#include <glib.h>
-#endif
-
class EventLoop;
/**
* An event that runs when the EventLoop has become idle, before
* waiting for more events. This class is not thread-safe; all
* methods must be run from EventLoop's thread.
+ *
+ * This class is not thread-safe, all methods must be called from the
+ * thread that runs the #EventLoop, except where explicitly documented
+ * as thread-safe.
*/
class IdleMonitor {
-#ifdef USE_EPOLL
friend class EventLoop;
-#endif
EventLoop &loop;
-#ifdef USE_EPOLL
bool active;
-#else
- guint source_id;
-#endif
public:
-#ifdef USE_EPOLL
IdleMonitor(EventLoop &_loop)
:loop(_loop), active(false) {}
-#else
- IdleMonitor(EventLoop &_loop)
- :loop(_loop), source_id(0) {}
-#endif
~IdleMonitor() {
- Cancel();
+#ifndef NDEBUG
+ /* this check is redundant, it is only here to avoid
+ the assertion in Cancel() */
+ if (IsActive())
+#endif
+ Cancel();
}
EventLoop &GetEventLoop() const {
@@ -64,11 +58,7 @@ public:
}
bool IsActive() const {
-#ifdef USE_EPOLL
return active;
-#else
- return source_id != 0;
-#endif
}
void Schedule();
@@ -79,9 +69,6 @@ protected:
private:
void Run();
-#ifndef USE_EPOLL
- static gboolean Callback(gpointer data);
-#endif
};
#endif /* MAIN_NOTIFY_H */
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index 5aa24aea2..4ded68ff4 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,14 @@ EventLoop::Run()
return;
}
- if (!idle_empty)
+ /* try to handle DeferredMonitors without WakeFD
+ overhead */
+ mutex.lock();
+ HandleDeferred();
+ busy = false;
+ mutex.unlock();
+
+ if (again)
/* re-evaluate timers because one of the
IdleMonitors may have added a new
timeout */
@@ -164,101 +189,107 @@ EventLoop::Run()
/* wait for new event */
- const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms);
- n_events = std::max(n, 0);
+ poll_group.ReadEvents(poll_result, timeout_ms);
now_ms = ::MonotonicClockMS();
- assert(!quit);
+ mutex.lock();
+ busy = true;
+ mutex.unlock();
/* invoke sockets */
-
- for (int i = 0; i < n; ++i) {
- const auto &e = events[i];
-
- if (e.events != 0) {
- SocketMonitor &m = *(SocketMonitor *)e.data.ptr;
- m.Dispatch(e.events);
-
+ for (int i = 0; i < poll_result.GetSize(); ++i) {
+ auto events = poll_result.GetEvents(i);
+ if (events != 0) {
if (quit)
break;
+
+ auto m = (SocketMonitor *)poll_result.GetObject(i);
+ m->Dispatch(events);
}
}
- n_events = 0;
+ poll_result.Reset();
+
} while (!quit);
-#else
- g_main_loop_run(loop);
-#endif
+#ifndef NDEBUG
+ assert(busy);
assert(thread.IsInside());
+ thread = ThreadId::Null();
+#endif
}
-#ifdef USE_EPOLL
-
void
-EventLoop::AddCall(std::function<void()> &&f)
+EventLoop::AddDeferred(DeferredMonitor &d)
{
mutex.lock();
- calls.push_back(f);
+ if (d.pending) {
+ mutex.unlock();
+ return;
+ }
+
+ assert(std::find(deferred.begin(),
+ deferred.end(), &d) == deferred.end());
+
+ /* we don't need to wake up the EventLoop if another
+ DeferredMonitor has already done it */
+ const bool must_wake = !busy && deferred.empty();
+
+ d.pending = true;
+ deferred.push_back(&d);
+ again = true;
mutex.unlock();
- wake_fd.Write();
+ if (must_wake)
+ wake_fd.Write();
}
-bool
-EventLoop::OnSocketReady(gcc_unused unsigned flags)
+void
+EventLoop::RemoveDeferred(DeferredMonitor &d)
{
- assert(!quit);
+ const ScopeLock protect(mutex);
- wake_fd.Read();
+ if (!d.pending) {
+ assert(std::find(deferred.begin(),
+ deferred.end(), &d) == deferred.end());
+ return;
+ }
- mutex.lock();
+ d.pending = false;
- while (!calls.empty() && !quit) {
- auto f = std::move(calls.front());
- calls.pop_front();
+ auto i = std::find(deferred.begin(), deferred.end(), &d);
+ assert(i != deferred.end());
+
+ deferred.erase(i);
+}
+
+void
+EventLoop::HandleDeferred()
+{
+ while (!deferred.empty() && !quit) {
+ DeferredMonitor &m = *deferred.front();
+ assert(m.pending);
+
+ deferred.pop_front();
+ m.pending = false;
mutex.unlock();
- f();
+ m.RunDeferred();
mutex.lock();
}
-
- mutex.unlock();
-
- return true;
}
-#else
-
-guint
-EventLoop::AddIdle(GSourceFunc function, gpointer data)
+bool
+EventLoop::OnSocketReady(gcc_unused unsigned flags)
{
- GSource *source = g_idle_source_new();
- g_source_set_callback(source, function, data, nullptr);
- guint id = g_source_attach(source, GetContext());
- g_source_unref(source);
- return id;
-}
+ assert(IsInside());
-GSource *
-EventLoop::AddTimeout(guint interval_ms,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new(interval_ms);
- g_source_set_callback(source, function, data, nullptr);
- g_source_attach(source, GetContext());
- return source;
-}
+ wake_fd.Read();
-GSource *
-EventLoop::AddTimeoutSeconds(guint interval_s,
- GSourceFunc function, gpointer data)
-{
- GSource *source = g_timeout_source_new_seconds(interval_s);
- g_source_set_callback(source, function, data, nullptr);
- g_source_attach(source, GetContext());
- return source;
-}
+ mutex.lock();
+ HandleDeferred();
+ mutex.unlock();
-#endif
+ return true;
+}
diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx
index 62e733747..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 781d29181..ce70a969b 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 cffad6b92..e04af3e4e 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,31 +45,11 @@ TimeoutMonitor::ScheduleSeconds(unsigned s)
{
Cancel();
-#ifdef USE_EPOLL
Schedule(s * 1000u);
-#else
- source = loop.AddTimeoutSeconds(s, Callback, this);
-#endif
}
void
TimeoutMonitor::Run()
{
-#ifndef USE_EPOLL
- Cancel();
-#endif
-
OnTimeout();
}
-
-#ifndef USE_EPOLL
-
-gboolean
-TimeoutMonitor::Callback(gpointer data)
-{
- TimeoutMonitor &monitor = *(TimeoutMonitor *)data;
- monitor.Run();
- return false;
-}
-
-#endif
diff --git a/src/event/TimeoutMonitor.hxx b/src/event/TimeoutMonitor.hxx
index 98e4e5564..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 cb52b86ca..000000000
--- a/src/filter/ChainFilterPlugin.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 "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 AudioFormat Open(AudioFormat &af, Error &error) override;
- virtual void Close();
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error);
-
-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..131c3b3f5
--- /dev/null
+++ b/src/filter/FilterInternal.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.
+ */
+
+/** \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/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 6c4f6b0e5..000000000
--- a/src/filter/NormalizeFilterPlugin.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 "FilterPlugin.hxx"
-#include "FilterInternal.hxx"
-#include "FilterRegistry.hxx"
-#include "pcm/PcmBuffer.hxx"
-#include "AudioFormat.hxx"
-#include "AudioCompress/compress.h"
-
-#include <assert.h>
-#include <string.h>
-
-class NormalizeFilter final : public Filter {
- struct Compressor *compressor;
-
- PcmBuffer buffer;
-
-public:
- virtual AudioFormat Open(AudioFormat &af, Error &error) override;
- virtual void Close();
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error);
-};
-
-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 b2dcde4cc..000000000
--- a/src/filter/ReplayGainFilterPlugin.cxx
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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 AudioFormat Open(AudioFormat &af, Error &error) override;
- virtual void Close();
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error);
-};
-
-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 d9042c21f..000000000
--- a/src/filter/RouteFilterPlugin.cxx
+++ /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.
- */
-
-/** \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 AudioFormat Open(AudioFormat &af, Error &error) override;
- virtual void Close();
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error);
-};
-
-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 1b663f6eb..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;
- }
-
- virtual AudioFormat Open(AudioFormat &af, Error &error) override;
- virtual void Close();
- virtual const void *FilterPCM(const void *src, size_t src_size,
- size_t *dest_size_r, Error &error);
-};
-
-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..cdeeefdc6
--- /dev/null
+++ b/src/filter/plugins/AutoConvertFilterPlugin.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 "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 <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);
+
+ 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();
+}
+
+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/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..7dc6db667
--- /dev/null
+++ b/src/filter/plugins/ChainFilterPlugin.cxx
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 <list>
+
+#include <assert.h>
+
+class ChainFilter final : public Filter {
+ struct Child {
+ const char *name;
+ Filter *filter;
+
+ Child(const char *_name, Filter *_filter)
+ :name(_name), filter(_filter) {}
+ ~Child() {
+ delete filter;
+ }
+
+ Child(const Child &) = delete;
+ Child &operator=(const Child &) = delete;
+ };
+
+ std::list<Child> children;
+
+public:
+ void Append(const char *name, Filter *filter) {
+ children.emplace_back(name, filter);
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+
+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/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..27e6774f8
--- /dev/null
+++ b/src/filter/plugins/ConvertFilterPlugin.cxx
@@ -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.
+ */
+
+#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 "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 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();
+}
+
+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));
+}
+
+const void *
+ConvertFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error)
+{
+ assert(in_audio_format.IsValid());
+
+ if (!out_audio_format.IsValid()) {
+ /* optimized special case: no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ return state->Convert(src, src_size, dest_size_r,
+ 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..58eb0c6a2
--- /dev/null
+++ b/src/filter/plugins/NormalizeFilterPlugin.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 "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "AudioCompress/compress.h"
+
+#include <string.h>
+
+class NormalizeFilter final : public Filter {
+ struct Compressor *compressor;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+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/plugins/NullFilterPlugin.cxx b/src/filter/plugins/NullFilterPlugin.cxx
new file mode 100644
index 000000000..f79aa19f7
--- /dev/null
+++ b/src/filter/plugins/NullFilterPlugin.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.
+ */
+
+/** \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"
+
+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/plugins/ReplayGainFilterPlugin.cxx b/src/filter/plugins/ReplayGainFilterPlugin.cxx
new file mode 100644
index 000000000..a5d9668cc
--- /dev/null
+++ b/src/filter/plugins/ReplayGainFilterPlugin.cxx
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+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();
+}
+
+const void *
+ReplayGainFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ const auto dest = pv.Apply({src, src_size});
+ *dest_size_r = dest.size;
+ return dest.data;
+}
+
+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..38c4ec43b
--- /dev/null
+++ b/src/filter/plugins/RouteFilterPlugin.cxx
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 <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 AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+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/plugins/VolumeFilterPlugin.cxx b/src/filter/plugins/VolumeFilterPlugin.cxx
new file mode 100644
index 000000000..c9b7aa89e
--- /dev/null
+++ b/src/filter/plugins/VolumeFilterPlugin.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.
+ */
+
+#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 AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+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();
+}
+
+const void *
+VolumeFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ const auto dest = pv.Apply({src, src_size});
+ *dest_size_r = dest.size;
+ return dest.data;
+}
+
+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 37b79a685..30ce7e3a9 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,29 +24,32 @@
#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
return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8));
+#else
+ return FromFS(path_utf8);
+#endif
}
AllocatedPath
@@ -64,7 +67,7 @@ AllocatedPath::FromUTF8(const char *path_utf8, Error &error)
AllocatedPath
AllocatedPath::GetDirectoryName() const
{
- return AllocatedPath(Donate(), g_path_get_dirname(c_str()));
+ return FromFS(PathTraitsFS::GetParent(c_str()));
}
std::string
@@ -82,14 +85,14 @@ AllocatedPath::RelativeFS(const char *other_fs) const
other_fs += l;
if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
+ if (!PathTraitsFS::IsSeparator(*other_fs))
/* mismatch */
return nullptr;
/* skip remaining path separators */
do {
++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
+ } while (PathTraitsFS::IsSeparator(*other_fs));
}
return other_fs;
@@ -101,7 +104,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..4fb217547 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,6 +53,12 @@ class AllocatedPath {
AllocatedPath(const_pointer _value):value(_value) {}
+ AllocatedPath(string &&_value):value(std::move(_value)) {}
+
+ static AllocatedPath Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size) {
+ return AllocatedPath(PathTraitsFS::Build(a, a_size, b, b_size));
+ }
public:
/**
* Copy a #AllocatedPath object.
@@ -67,6 +70,8 @@ public:
*/
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,6 +138,15 @@ public:
}
/**
+ * Convert a C++ string that is already in the filesystem
+ * character set to a #Path instance.
+ */
+ gcc_pure
+ static AllocatedPath FromFS(string &&fs) {
+ return AllocatedPath(std::move(fs));
+ }
+
+ /**
* Convert a UTF-8 C string to a #AllocatedPath instance.
* Returns return a "nulled" instance on error.
*/
@@ -215,7 +245,7 @@ public:
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx
index dad5779f9..2d289c3b8 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,11 +22,14 @@
#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>
@@ -41,6 +44,7 @@
*/
static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1;
+#ifdef HAVE_GLIB
static std::string fs_charset;
gcc_pure
@@ -70,10 +74,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
@@ -81,8 +104,14 @@ PathToUTF8(const char *path_fs)
{
assert(path_fs != nullptr);
- 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))
@@ -103,9 +132,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)
{
@@ -118,3 +152,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..6456c0254
--- /dev/null
+++ b/src/fs/StandardDirectory.cxx
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with 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/CharUtil.hxx"
+#include "util/StringUtil.hxx"
+#include "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(const char *line, const char *dir_name,
+ AllocatedPath &result_dir)
+{
+ // strip leading white space
+ line = strchug_fast(line);
+
+ // check for end-of-line or comment
+ if (*line == '\0' || *line == '#')
+ return false;
+
+ // check if current setting is for requested dir
+ if (!StringStartsWith(line, dir_name))
+ return false;
+ line += strlen(dir_name);
+
+ // strip equals sign and spaces around it
+ line = strchug_fast(line);
+ if (*line != '=')
+ return false;
+ ++line;
+ line = strchug_fast(line);
+
+ // check if path is quoted
+ bool quoted = false;
+ if (*line == '"') {
+ ++line;
+ quoted = true;
+ }
+
+ // check if path is relative to $HOME
+ bool home_relative = false;
+ if (StringStartsWith(line, home_prefix)) {
+ line += strlen(home_prefix);
+ home_relative = true;
+ }
+
+
+ const char *line_end;
+ // find end of the string
+ if (quoted) {
+ line_end = strrchr(line, '"');
+ if (line_end == nullptr)
+ return true;
+ } else {
+ line_end = line + strlen(line);
+ while (line < line_end && IsWhitespaceNotNull(line_end[-1]))
+ --line_end;
+ }
+
+ // check for empty result
+ if (line == line_end)
+ return true;
+
+ // build the result path
+ std::string path(line, line_end);
+
+ auto result = AllocatedPath::Null();
+ if (home_relative) {
+ auto home = GetHomeDir();
+ if (home.IsNull())
+ return true;
+ result = AllocatedPath::Build(home, path.c_str());
+ } else {
+ result = AllocatedPath::FromFS(std::move(path));
+ }
+
+ if (IsValidDir(result.c_str())) {
+ result_dir = std::move(result);
+ return true;
+ }
+ return true;
+}
+
+static AllocatedPath GetUserDir(const char *name)
+{
+ auto result = AllocatedPath::Null();
+ auto config_dir = GetUserConfigDir();
+ if (config_dir.IsNull())
+ return result;
+ auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs");
+ TextFile input(dirs_file);
+ if (input.HasFailed())
+ return result;
+ const char *line;
+ while ((line = input.ReadLine()) != nullptr)
+ if (ParseConfigLine(line, name, result))
+ return result;
+ return result;
+}
+
+#endif
+
+AllocatedPath GetUserConfigDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_LOCAL_APPDATA);
+#elif defined(USE_XDG)
+ // Check for $XDG_CONFIG_HOME
+ auto config_home = getenv("XDG_CONFIG_HOME");
+ if (IsValidPathString(config_home) && IsValidDir(config_home))
+ return AllocatedPath::FromFS(config_home);
+
+ // Check for $HOME/.config
+ auto home = GetHomeDir();
+ if (!home.IsNull()) {
+ AllocatedPath fallback = AllocatedPath::Build(home, ".config");
+ if (IsValidDir(fallback.c_str()))
+ return fallback;
+ }
+
+ return AllocatedPath::Null();
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath GetUserMusicDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_MYMUSIC);
+#elif defined(USE_XDG)
+ return GetUserDir("XDG_MUSIC_DIR");
+#elif defined(ANDROID)
+ return Environment::getExternalStoragePublicDirectory("Music");
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath
+GetUserCacheDir()
+{
+#ifdef USE_XDG
+ return GetUserDir("XDG_CACHE_DIR");
+#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/TextFile.cxx b/src/fs/TextFile.cxx
new file mode 100644
index 000000000..b1a92b9cc
--- /dev/null
+++ b/src/fs/TextFile.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 "TextFile.hxx"
+#include "util/Alloc.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+TextFile::TextFile(Path path_fs)
+ :file(FOpen(path_fs, FOpenMode::ReadText)),
+ buffer((char *)xalloc(step)), capacity(step), length(0) {}
+
+TextFile::~TextFile()
+{
+ free(buffer);
+
+ if (file != nullptr)
+ fclose(file);
+}
+
+char *
+TextFile::ReadLine()
+{
+ assert(file != nullptr);
+
+ while (true) {
+ if (length >= capacity) {
+ if (capacity >= max_length)
+ /* too large already - bail out */
+ return nullptr;
+
+ capacity <<= 1;
+ char *new_buffer = (char *)realloc(buffer, capacity);
+ if (new_buffer == nullptr)
+ /* out of memory - bail out */
+ return nullptr;
+ }
+
+ char *p = fgets(buffer + length, capacity - length, file);
+ if (p == nullptr) {
+ if (length == 0 || ferror(file))
+ return nullptr;
+ break;
+ }
+
+ length += strlen(buffer + length);
+ if (buffer[length - 1] == '\n')
+ break;
+ }
+
+ /* remove the newline characters */
+ if (buffer[length - 1] == '\n')
+ --length;
+ if (buffer[length - 1] == '\r')
+ --length;
+
+ buffer[length] = 0;
+ length = 0;
+ return buffer;
+}
diff --git a/src/fs/TextFile.hxx b/src/fs/TextFile.hxx
new file mode 100644
index 000000000..e3a712a88
--- /dev/null
+++ b/src/fs/TextFile.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_TEXT_FILE_HXX
+#define MPD_TEXT_FILE_HXX
+
+#include "Compiler.h"
+
+#include <stdio.h>
+#include <stddef.h>
+
+class Path;
+
+class TextFile {
+ static constexpr size_t max_length = 512 * 1024;
+ static constexpr size_t step = 1024;
+
+ FILE *const file;
+
+ char *buffer;
+ size_t capacity, length;
+
+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/fs/Traits.cxx b/src/fs/Traits.cxx
index 2c3ce075b..d62987087 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,24 +22,136 @@
#include <string.h>
-const char *
-PathTraits::GetBaseUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+BuildPathImpl(typename Traits::const_pointer a, size_t a_size,
+ typename Traits::const_pointer b, size_t b_size)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+ if (a_size == 0)
+ return typename Traits::string(b, b_size);
+ if (b_size == 0)
+ return typename Traits::string(a, a_size);
+
+ typename Traits::string result(a, a_size);
+
+ if (!Traits::IsSeparator(a[a_size - 1]))
+ result.push_back(Traits::SEPARATOR);
+
+ if (Traits::IsSeparator(b[0]))
+ result.append(b + 1, b_size - 1);
+ else
+ result.append(b, b_size);
+
+ return result;
+}
+
+template<typename Traits>
+typename Traits::const_pointer
+GetBasePathImpl(typename Traits::const_pointer p)
{
assert(p != nullptr);
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? slash + 1
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ return sep != nullptr
+ ? sep + 1
: p;
}
-std::string
-PathTraits::GetParentUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+GetParentPathImpl(typename Traits::const_pointer p)
{
assert(p != nullptr);
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? std::string(p, slash)
- : std::string(".");
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ if (sep == nullptr)
+ return typename Traits::string(".");
+ if (sep == p)
+ return typename Traits::string(p, p + 1);
+#ifdef WIN32
+ if (Traits::IsDrive(p) && sep == p + 2)
+ return typename Traits::string(p, p + 3);
+#endif
+ return typename Traits::string(p, sep);
+}
+
+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..88715c3e8 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;
#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 char *pointer;
+ typedef const char *const_pointer;
+
+ static constexpr value_type SEPARATOR = '/';
+
+ static constexpr bool IsSeparator(value_type ch) {
+ return ch == SEPARATOR;
+ }
+
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
assert(p != nullptr);
+ return strrchr(p, SEPARATOR);
+ }
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorUTF8(*p);
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
+#endif
+
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+ assert(p != nullptr);
+#ifdef WIN32
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
#endif
+ return IsSeparator(*p);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
}
/**
@@ -92,7 +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/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..8942b5116
--- /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 "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())
+ return false;
+
+ if (new_offset < 0)
+ 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..c2055c17d
--- /dev/null
+++ b/src/input/AsyncInputStream.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_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:
+ void SetTag(Tag *_tag);
+
+ void Pause();
+
+ void SetClosed() {
+ open = false;
+ }
+
+ void PostponeError(Error &&error);
+
+ bool IsBufferEmpty() const {
+ return buffer.IsEmpty();
+ }
+
+ bool IsBufferFull() const {
+ return buffer.IsFull();
+ }
+
+ gcc_pure
+ size_t GetBufferSpace() const {
+ return buffer.GetSpace();
+ }
+
+ void AppendToBuffer(const void *data, size_t append_size);
+
+ 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;
+ }
+
+ 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 b78545951..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, 1);
- curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
- curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
- curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
- curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
- curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
- curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
- curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
-
- if (proxy != 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/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..6a85f9ffd
--- /dev/null
+++ b/src/input/IcyInputStream.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_ICY_INPUT_STREAM_HXX
+#define MPD_ICY_INPUT_STREAM_HXX
+
+#include "ProxyInputStream.hxx"
+#include "IcyMetaDataParser.hxx"
+
+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..5e64dcaed
--- /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..1bdf44b77
--- /dev/null
+++ b/src/input/InputPlugin.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_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,
+ };
+
+ 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
+ */
+ 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..657b9df09
--- /dev/null
+++ b/src/input/InputStream.cxx
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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/UriUtil.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();
+}
+
+bool
+InputStream::CheapSeeking() const
+{
+ return IsSeekable() && !uri_has_scheme(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)
+{
+ assert(ptr != nullptr);
+ 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..25e99de3d
--- /dev/null
+++ b/src/input/InputStream.hxx
@@ -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.
+ */
+
+#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;
+
+class InputStream {
+public:
+ typedef int64_t 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;
+
+ /**
+ * the size of the resource, or -1 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(-1), 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();
+ }
+
+ 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 >= 0;
+ }
+
+ gcc_pure
+ offset_type GetSize() const {
+ assert(ready);
+
+ return size;
+ }
+
+ void AddOffset(offset_type delta) {
+ assert(ready);
+ assert(offset >= 0);
+ assert(delta >= 0);
+
+ offset += delta;
+ }
+
+ gcc_pure
+ offset_type GetOffset() const {
+ assert(ready);
+
+ return offset;
+ }
+
+ gcc_pure
+ offset_type GetRest() const {
+ assert(ready);
+ assert(size >= 0);
+ assert(offset >= 0);
+
+ 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/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/Open.cxx b/src/input/Open.cxx
new file mode 100644
index 000000000..6e89569d6
--- /dev/null
+++ b/src/input/Open.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 "InputStream.hxx"
+#include "Registry.hxx"
+#include "InputPlugin.hxx"
+#include "plugins/RewindInputPlugin.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+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) {
+ 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..d65fb5df1
--- /dev/null
+++ b/src/input/ProxyInputStream.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 "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.GetSize();
+ 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..d7cb440b3
--- /dev/null
+++ b/src/input/TextInputStream.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 "TextInputStream.hxx"
+#include "InputStream.hxx"
+#include "util/CharUtil.hxx"
+#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[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/input/TextInputStream.hxx b/src/input/TextInputStream.hxx
new file mode 100644
index 000000000..c67423da1
--- /dev/null
+++ b/src/input/TextInputStream.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_TEXT_INPUT_STREAM_HXX
+#define MPD_TEXT_INPUT_STREAM_HXX
+
+#include "util/FifoBuffer.hxx"
+
+#include <string>
+
+class InputStream;
+
+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/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx
new file mode 100644
index 000000000..dc08f3b6b
--- /dev/null
+++ b/src/input/ThreadInputStream.cxx
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 = Read(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)
+{
+ if (postponed_error.IsDefined()) {
+ error = std::move(postponed_error);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ThreadInputStream::IsAvailable()
+{
+ return !buffer->IsEmpty() || eof || postponed_error.IsDefined();
+}
+
+inline size_t
+ThreadInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ 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()
+{
+ 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..82b96f7df
--- /dev/null
+++ b/src/input/plugins/AlsaInputPlugin.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.
+ */
+
+/*
+ * 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 <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..b51dc6835
--- /dev/null
+++ b/src/input/plugins/ArchiveInputPlugin.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 "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>
+
+/**
+ * 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 ArchivePlugin *arplug;
+ InputStream *is;
+
+ if (!PathTraitsFS::IsAbsolute(pathname))
+ return nullptr;
+
+ char *pname = strdup(pathname);
+ // archive_lookup will modify pname when true is returned
+ const char *archive, *filename, *suffix;
+ if (!archive_lookup(pname, &archive, &filename, &suffix)) {
+ FormatDebug(archive_domain,
+ "not an archive, lookup %s failed", pname);
+ 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;
+}
+
+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..024723726
--- /dev/null
+++ b/src/input/plugins/ArchiveInputPlugin.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_ARCHIVE_HXX
+#define MPD_INPUT_ARCHIVE_HXX
+
+extern const struct InputPlugin input_plugin_archive;
+
+#endif
diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx
new file mode 100644
index 000000000..6f1ea976a
--- /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 < 0 || 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..46961d08f
--- /dev/null
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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/CharUtil.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();
+
+ 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 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,
+ "");
+ }
+
+ 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);
+
+ curl_global_cleanup();
+}
+
+CurlInputStream::~CurlInputStream()
+{
+ FreeEasyIndirect();
+}
+
+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;
+ 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 */
+
+ while (value < end && IsWhitespaceOrNull(*value))
+ ++value;
+
+ while (end > value && IsWhitespaceOrNull(end[-1]))
+ --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, 1);
+ curl_easy_setopt(easy, CURLOPT_NETRC, 1);
+ curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 5);
+ curl_easy_setopt(easy, CURLOPT_FAILONERROR, true);
+ 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);
+ }
+
+ 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..6e0260aee
--- /dev/null
+++ b/src/input/plugins/FfmpegInputPlugin.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.
+ */
+
+/* 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/StringUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.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 constexpr Domain ffmpeg_domain("ffmpeg");
+
+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;
+ 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;
+ }
+
+ return new FfmpegInputStream(uri, mutex, cond, h);
+}
+
+size_t
+FfmpegInputStream::Read(void *ptr, size_t read_size, Error &error)
+{
+ int ret = avio_read(h, (unsigned char *)ptr, read_size);
+ if (ret <= 0) {
+ if (ret < 0)
+ error.Set(ffmpeg_domain, "avio_read() failed");
+
+ eof = true;
+ return false;
+ }
+
+ offset += ret;
+ return (size_t)ret;
+}
+
+bool
+FfmpegInputStream::IsEOF()
+{
+ return eof;
+}
+
+bool
+FfmpegInputStream::Seek(offset_type new_offset, Error &error)
+{
+ int64_t ret = avio_seek(h, new_offset, SEEK_SET);
+
+ if (ret >= 0) {
+ 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,
+};
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..60c316567
--- /dev/null
+++ b/src/input/plugins/FileInputPlugin.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 "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>
+
+static constexpr Domain file_domain("file");
+
+struct FileInputStream final : public InputStream {
+ int fd;
+
+ 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;
+};
+
+static InputStream *
+input_file_open(const char *filename,
+ Mutex &mutex, Cond &cond,
+ Error &error)
+{
+ int fd, ret;
+ struct stat st;
+
+ if (!PathTraitsFS::IsAbsolute(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
+
+ return new FileInputStream(filename, fd, st.st_size, mutex, cond);
+}
+
+bool
+FileInputStream::Seek(offset_type new_offset, Error &error)
+{
+ new_offset = (offset_type)lseek(fd, (off_t)new_offset, SEEK_SET);
+ if (new_offset < 0) {
+ error.SetErrno("Failed to seek");
+ return false;
+ }
+
+ offset = new_offset;
+ 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..4aef94637
--- /dev/null
+++ b/src/input/plugins/FileInputPlugin.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_FILE_HXX
+#define MPD_INPUT_FILE_HXX
+
+extern const struct InputPlugin input_plugin_file;
+
+#endif
diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx
new file mode 100644
index 000000000..1aed9c662
--- /dev/null
+++ b/src/input/plugins/MmsInputPlugin.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 "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)
+{
+ 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..baa707738
--- /dev/null
+++ b/src/input/plugins/NfsInputPlugin.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 "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;
+
+public:
+ NfsInputStream(const char *_uri,
+ Mutex &_mutex, Cond &_cond,
+ void *_buffer)
+ :AsyncInputStream(_uri, _mutex, _cond,
+ _buffer, NFS_MAX_BUFFERED,
+ NFS_RESUME_AT) {}
+
+ 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()
+{
+ 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);
+
+ 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);
+ postponed_error = std::move(error);
+
+ if (IsSeekPending())
+ SeekDone();
+ else if (!IsReady())
+ SetReady();
+}
+
+/*
+ * 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..3636fc7c6
--- /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_HPP
+
+#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..47a83e49b
--- /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.SetTime(track.length / 1000);
+
+ 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/icu/Collate.cxx b/src/lib/icu/Collate.cxx
new file mode 100644
index 000000000..f6621eb21
--- /dev/null
+++ b/src/lib/icu/Collate.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 "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>
+
+#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/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..50762b582
--- /dev/null
+++ b/src/lib/nfs/Cancellable.hxx
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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 <list>
+#include <algorithm>
+
+#include <assert.h>
+
+template<typename T>
+class CancellablePointer {
+public:
+ typedef T *pointer_type;
+ typedef T &reference_type;
+ typedef const T &const_reference_type;
+
+private:
+ pointer_type p;
+
+public:
+ explicit constexpr 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 std::list<CT> 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));
+ }
+
+ class MatchReference {
+ const CT &c;
+
+ public:
+ constexpr explicit MatchReference(const CT &_c):c(_c) {}
+
+ gcc_pure
+ bool operator()(const CT &a) const {
+ return &a == &c;
+ }
+ };
+
+ gcc_pure
+ iterator Find(CT &c) {
+ return std::find_if(list.begin(), list.end(),
+ MatchReference(c));
+ }
+
+ gcc_pure
+ const_iterator Find(const CT &c) const {
+ return std::find_if(list.begin(), list.end(),
+ MatchReference(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());
+
+ list.emplace_back(p, std::forward<Args>(args)...);
+ return list.back();
+ }
+
+ void RemoveLast() {
+ list.pop_back();
+ }
+
+ bool RemoveOptional(CT &ct) {
+ auto i = Find(ct);
+ if (i == list.end())
+ return false;
+
+ list.erase(i);
+ return true;
+ }
+
+ void Remove(CT &ct) {
+ auto i = Find(ct);
+ assert(i != list.end());
+
+ list.erase(i);
+ }
+
+ void Cancel(reference_type p) {
+ auto i = Find(p);
+ assert(i != list.end());
+
+ i->Cancel();
+ }
+};
+
+#endif
diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx
new file mode 100644
index 000000000..a2f0cbb45
--- /dev/null
+++ b/src/lib/nfs/Connection.cxx
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "system/fd_util.h"
+#include "util/Error.hxx"
+#include "event/Call.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+}
+
+#include <utility>
+
+#include <poll.h> /* for POLLIN, POLLOUT */
+
+inline bool
+NfsConnection::CancellableCallback::Open(nfs_context *ctx,
+ const char *path, int flags,
+ Error &error)
+{
+ 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)
+{
+ 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)
+{
+ 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::Callback(int err, void *data)
+{
+ if (!IsCancelled()) {
+ 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 {
+ 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(new_leases.empty());
+ assert(active_leases.empty());
+ assert(callbacks.IsEmpty());
+
+ if (context != nullptr)
+ BlockingCall(SocketMonitor::GetEventLoop(), [this](){
+ DestroyContext();
+ });
+}
+
+void
+NfsConnection::AddLease(NfsLease &lease)
+{
+ {
+ const ScopeLock protect(mutex);
+ new_leases.push_back(&lease);
+ }
+
+ DeferredMonitor::Schedule();
+}
+
+void
+NfsConnection::RemoveLease(NfsLease &lease)
+{
+ const ScopeLock protect(mutex);
+
+ new_leases.remove(&lease);
+ active_leases.remove(&lease);
+}
+
+bool
+NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
+ Error &error)
+{
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this);
+ if (!c.Open(context, path, flags, error)) {
+ callbacks.RemoveLast();
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error)
+{
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this);
+ if (!c.Stat(context, fh, error)) {
+ callbacks.RemoveLast();
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+bool
+NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size,
+ NfsCallback &callback, Error &error)
+{
+ assert(!callbacks.Contains(callback));
+
+ auto &c = callbacks.Add(callback, *this);
+ if (!c.Read(context, fh, offset, size, error)) {
+ callbacks.RemoveLast();
+ return false;
+ }
+
+ ScheduleSocket();
+ return true;
+}
+
+void
+NfsConnection::Cancel(NfsCallback &callback)
+{
+ callbacks.Cancel(callback);
+}
+
+static void
+DummyCallback(int, struct nfs_context *, void *, void *)
+{
+}
+
+void
+NfsConnection::Close(struct nfsfh *fh)
+{
+ nfs_close_async(context, fh, DummyCallback, nullptr);
+ ScheduleSocket();
+}
+
+void
+NfsConnection::DestroyContext()
+{
+ assert(context != nullptr);
+
+ SocketMonitor::Cancel();
+ nfs_destroy_context(context);
+ context = nullptr;
+}
+
+void
+NfsConnection::ScheduleSocket()
+{
+ assert(context != nullptr);
+
+ if (!SocketMonitor::IsDefined()) {
+ int _fd = nfs_get_fd(context);
+ fd_set_cloexec(_fd, true);
+ SocketMonitor::Open(_fd);
+ }
+
+ SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
+}
+
+bool
+NfsConnection::OnSocketReady(unsigned flags)
+{
+ 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();
+
+ assert(!in_event);
+ in_event = true;
+
+ assert(!in_service);
+ in_service = true;
+ postponed_destroy = false;
+
+ int result = nfs_service(context, events_to_libnfs(flags));
+
+ assert(context != nullptr);
+ assert(in_service);
+ in_service = false;
+
+ if (postponed_destroy) {
+ /* somebody has called nfs_client_free() while we were inside
+ nfs_service() */
+ const ScopeLock protect(mutex);
+ DestroyContext();
+ closed = true;
+ // TODO? nfs_client_cleanup_files(client);
+ } else if (!was_mounted && mount_finished) {
+ const ScopeLock protect(mutex);
+
+ 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));
+
+ const ScopeLock protect(mutex);
+
+ DestroyContext();
+ closed = true;
+
+ if (!mount_finished)
+ BroadcastMountError(std::move(error));
+ else
+ BroadcastError(std::move(error));
+ }
+
+ assert(in_event);
+ in_event = false;
+
+ if (context != nullptr)
+ ScheduleSocket();
+
+ return !closed;
+}
+
+inline void
+NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs,
+ gcc_unused void *data)
+{
+ assert(context == nfs);
+
+ mount_finished = true;
+
+ if (status < 0) {
+ postponed_mount_error.Set(nfs_domain, status,
+ "nfs_mount_async() failed");
+ 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)
+{
+ if (context != nullptr)
+ return true;
+
+ context = nfs_init_context();
+ if (context == nullptr) {
+ error.Set(nfs_domain, "nfs_init_context() failed");
+ return false;
+ }
+
+ postponed_mount_error.Clear();
+ mount_finished = false;
+ in_service = false;
+ in_event = false;
+
+ 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()
+{
+ 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)
+{
+ 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)
+{
+ while (!active_leases.empty()) {
+ auto l = active_leases.front();
+ active_leases.pop_front();
+ l->OnNfsConnectionDisconnected(error);
+ }
+
+ BroadcastMountError(std::move(error));
+}
+
+void
+NfsConnection::RunDeferred()
+{
+ {
+ Error error;
+ if (!MountInternal(error)) {
+ const ScopeLock protect(mutex);
+ BroadcastMountError(std::move(error));
+ return;
+ }
+ }
+
+ if (mount_finished) {
+ const ScopeLock protect(mutex);
+ BroadcastMountSuccess();
+ }
+}
diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx
new file mode 100644
index 000000000..cb4126d25
--- /dev/null
+++ b/src/lib/nfs/Connection.hxx
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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 "thread/Mutex.hxx"
+#include "event/SocketMonitor.hxx"
+#include "event/DeferredMonitor.hxx"
+#include "util/Error.hxx"
+
+#include <string>
+#include <list>
+
+struct nfs_context;
+class NfsCallback;
+
+/**
+ * An asynchronous connection to a NFS server.
+ */
+class NfsConnection : SocketMonitor, DeferredMonitor {
+ class CancellableCallback : public CancellablePointer<NfsCallback> {
+ NfsConnection &connection;
+
+ public:
+ explicit constexpr CancellableCallback(NfsCallback &_callback,
+ NfsConnection &_connection)
+ :CancellablePointer<NfsCallback>(_callback),
+ connection(_connection) {}
+
+ 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);
+
+ 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;
+
+ Mutex mutex;
+
+ typedef std::list<NfsLease *> LeaseList;
+ LeaseList new_leases, active_leases;
+
+ typedef CancellableList<NfsCallback, CancellableCallback> CallbackList;
+ CallbackList callbacks;
+
+ Error postponed_mount_error;
+
+ /**
+ * True when nfs_service() is being called. During that,
+ * nfs_client_free() is postponed, or libnfs will crash. See
+ * #postponed_destroy.
+ */
+ bool in_service;
+
+ /**
+ * True when OnSocketReady() is being called. During that,
+ * event updates are omitted.
+ */
+ bool in_event;
+
+ /**
+ * True when nfs_client_free() has been called while #in_service
+ * was true.
+ */
+ bool postponed_destroy;
+
+ bool mount_finished;
+
+public:
+ gcc_nonnull_all
+ NfsConnection(EventLoop &_loop,
+ const char *_server, const char *_export_name)
+ :SocketMonitor(_loop), DeferredMonitor(_loop),
+ server(_server), export_name(_export_name),
+ context(nullptr) {}
+
+#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8)
+ /* needed for NfsManager::GetConnection() due to lack of
+ std::map::emplace() */
+ NfsConnection(NfsConnection &&other)
+ :SocketMonitor(((SocketMonitor &)other).GetEventLoop()),
+ DeferredMonitor(((DeferredMonitor &)other).GetEventLoop()),
+ server(std::move(other.server)),
+ export_name(std::move(other.export_name)),
+ context(nullptr) {
+ assert(other.context == nullptr);
+ assert(other.new_leases.empty());
+ assert(other.active_leases.empty());
+ assert(other.callbacks.IsEmpty());
+ }
+#endif
+
+ ~NfsConnection();
+
+ gcc_pure
+ const char *GetServer() const {
+ return server.c_str();
+ }
+
+ gcc_pure
+ const char *GetExportName() const {
+ return export_name.c_str();
+ }
+
+ /**
+ * 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 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);
+
+protected:
+ virtual void OnNfsConnectionError(Error &&error) = 0;
+
+private:
+ void DestroyContext();
+ 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();
+
+ /* virtual methods from SocketMonitor */
+ virtual bool OnSocketReady(unsigned flags) override;
+
+ /* 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..52d951fa6
--- /dev/null
+++ b/src/lib/nfs/FileReader.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.
+ */
+
+#include "config.h"
+#include "FileReader.hxx"
+#include "Glue.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;
+ }
+
+ connection->RemoveLease(*this);
+
+ if (state > State::MOUNT && state != State::IDLE)
+ connection->Cancel(*this);
+
+ if (state > State::OPEN)
+ connection->Close(fh);
+
+ 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;
+ 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);
+
+ Error copy;
+ copy.Set(error);
+ OnNfsFileError(std::move(copy));
+}
+
+void
+NfsFileReader::OnNfsConnectionDisconnected(const Error &error)
+{
+ assert(state > State::MOUNT);
+
+ state = State::INITIAL;
+
+ 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)
+{
+ 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..7f43e0ecf
--- /dev/null
+++ b/src/lib/nfs/FileReader.hxx
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NFS_FILE_READER_HXX
+#define MPD_NFS_FILE_READER_HXX
+
+#include "check.h"
+#include "Lease.hxx"
+#include "Callback.hxx"
+#include "event/DeferredMonitor.hxx"
+
+#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:
+ 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..c043f06c6
--- /dev/null
+++ b/src/lib/nfs/Glue.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "Glue.hxx"
+#include "Manager.hxx"
+#include "IOThread.hxx"
+#include "util/Manual.hxx"
+
+class NfsGlue {
+ NfsManager manager;
+
+public:
+ NfsGlue(EventLoop &_loop)
+ :manager(_loop) {}
+
+ ~NfsGlue() {
+ //assert(open_uri.empty());
+ }
+
+ NfsConnection &GetConnection(const char *server, const char *export_name) {
+ return manager.GetConnection(server, export_name);
+ }
+};
+
+static Manual<NfsGlue> 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;
+
+ nfs_glue.Destruct();
+}
+
+NfsConnection &
+nfs_get_connection(const char *server, const char *export_name)
+{
+ 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..5c236552c
--- /dev/null
+++ b/src/lib/nfs/Manager.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "Manager.hxx"
+#include "event/Loop.hxx"
+#include "Log.hxx"
+
+void
+NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error)
+{
+ FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName());
+
+ manager.connections.erase(Key(GetServer(), GetExportName()));
+}
+
+NfsConnection &
+NfsManager::GetConnection(const char *server, const char *export_name)
+{
+ assert(server != nullptr);
+ assert(export_name != nullptr);
+ assert(loop.IsInside());
+
+ const std::string key = Key(server, export_name);
+
+#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8)
+ /* std::map::emplace() not available; this hack uses the move
+ constructor */
+ auto e = connections.insert(std::make_pair(key,
+ ManagedConnection(*this, loop,
+ server,
+ export_name)));
+#else
+ auto e = connections.emplace(std::piecewise_construct,
+ std::forward_as_tuple(key),
+ std::forward_as_tuple(*this, loop,
+ server,
+ export_name));
+#endif
+ return e.first->second;
+}
diff --git a/src/lib/nfs/Manager.hxx b/src/lib/nfs/Manager.hxx
new file mode 100644
index 000000000..11a779a2a
--- /dev/null
+++ b/src/lib/nfs/Manager.hxx
@@ -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.
+ */
+
+#ifndef MPD_NFS_MANAGER_HXX
+#define MPD_NFS_MANAGER_HXX
+
+#include "check.h"
+#include "Connection.hxx"
+
+#include <string>
+#include <map>
+
+/**
+ * A manager for NFS connections. Handles multiple connections to
+ * multiple NFS servers.
+ */
+class NfsManager {
+ class ManagedConnection final : public NfsConnection {
+ NfsManager &manager;
+
+ public:
+ ManagedConnection(NfsManager &_manager, EventLoop &_loop,
+ const char *_server,
+ const char *_export_name)
+ :NfsConnection(_loop, _server, _export_name),
+ manager(_manager) {}
+
+#if defined(__GNUC__) && !defined(__clang__) && !GCC_CHECK_VERSION(4,8)
+ /* needed due to lack of std::map::emplace() */
+ ManagedConnection(ManagedConnection &&other)
+ :NfsConnection(std::move(other)),
+ manager(other.manager) {}
+#endif
+
+ protected:
+ /* virtual methods from NfsConnection */
+ void OnNfsConnectionError(Error &&error) override;
+ };
+
+ EventLoop &loop;
+
+ /**
+ * Maps server+":"+export_name (see method Key()) to
+ * #ManagedConnection.
+ */
+ std::map<std::string, ManagedConnection> connections;
+
+public:
+ NfsManager(EventLoop &_loop)
+ :loop(_loop) {}
+
+ gcc_pure
+ NfsConnection &GetConnection(const char *server,
+ const char *export_name);
+
+private:
+ gcc_pure
+ static std::string Key(const char *server, const char *export_name) {
+ return std::string(server) + ':' + export_name;
+ }
+};
+
+#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..ef8757ec5
--- /dev/null
+++ b/src/lib/upnp/ContentDirectoryService.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 "ContentDirectoryService.hxx"
+#include "Domain.hxx"
+#include "Device.hxx"
+#include "ixmlwrap.hxx"
+#include "Util.hxx"
+#include "Action.hxx"
+#include "util/Error.hxx"
+
+ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device,
+ const UPnPService &service)
+ :m_actionURL(caturl(device.URLBase, service.controlURL)),
+ m_serviceType(service.serviceType),
+ m_deviceId(device.UDN),
+ m_friendlyName(device.friendlyName),
+ m_manufacturer(device.manufacturer),
+ m_modelName(device.modelName),
+ m_rdreqcnt(200)
+{
+ if (!m_modelName.compare("MediaTomb")) {
+ // Readdir by 200 entries is good for most, but MediaTomb likes
+ // them really big. Actually 1000 is better but I don't dare
+ m_rdreqcnt = 500;
+ }
+}
+
+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..9ea78c624
--- /dev/null
+++ b/src/lib/upnp/Discovery.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 "Discovery.hxx"
+#include "Domain.hxx"
+#include "ContentDirectoryService.hxx"
+#include "system/Clock.hxx"
+#include "Log.hxx"
+
+#include <upnp/upnptools.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..af07daf61
--- /dev/null
+++ b/src/lib/upnp/Discovery.hxx
@@ -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.
+ */
+
+#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 <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..cf34a47d3
--- /dev/null
+++ b/src/lib/upnp/Util.cxx
@@ -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.
+ */
+
+#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());
+}
+
+std::string
+caturl(const std::string &s1, const std::string &s2)
+{
+ if (s2.front() == '/') {
+ /* absolute path: replace the whole URI path in s1 */
+
+ auto i = s1.find("://");
+ if (i == s1.npos)
+ /* no scheme: override s1 completely */
+ return s2;
+
+ /* find the first slash after the host part */
+ i = s1.find('/', i + 3);
+ if (i == s1.npos)
+ /* there's no URI path - simply append s2 */
+ i = s1.length();
+
+ return s1.substr(0, i) + s2;
+ }
+
+ std::string out(s1);
+ if (out.back() != '/')
+ out.push_back('/');
+
+ out += s2;
+ return out;
+}
+
+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..58e382faa
--- /dev/null
+++ b/src/lib/upnp/Util.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_UTIL_HXX
+#define MPD_UPNP_UTIL_HXX
+
+#include "Compiler.h"
+
+#include <string>
+#include <list>
+
+std::string
+caturl(const std::string& s1, const std::string& s2);
+
+void
+trimstring(std::string &s, const char *ws = " \t\n");
+
+std::string
+path_getfather(const std::string &s);
+
+gcc_pure
+std::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/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..596b3c12a
--- /dev/null
+++ b/src/mixer/Volume.cxx
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "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(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/mixer/Volume.hxx b/src/mixer/Volume.hxx
new file mode 100644
index 000000000..a02c21a21
--- /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"
+
+#include <stdio.h>
+
+class MultipleOutputs;
+
+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(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/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 6b9e95368..ebd12e5c6 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;
-#ifndef WIN32
+#if !defined(WIN32) && !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 6668c920f..000000000
--- a/src/output/AlsaOutputPlugin.cxx
+++ /dev/null
@@ -1,858 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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 % 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;
- }
- }
-
- chunk = ad->pcm_export->Export(chunk, size, size);
-
- assert(size % ad->out_frame_size == 0);
-
- size /= ad->out_frame_size;
-
- while (true) {
- snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0) {
- ad->period_position = (ad->period_position + ret)
- % ad->period_frames;
-
- size_t bytes_written = ret * ad->out_frame_size;
- return ad->pcm_export->CalcSourceSize(bytes_written);
- }
-
- if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
- alsa_recover(ad, ret) < 0) {
- 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..eafcec432
--- /dev/null
+++ b/src/output/Init.cxx
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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),
+ 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..266930448
--- /dev/null
+++ b/src/output/Internal.hxx
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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 music_chunk;
+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 #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 music_chunk *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);
+ 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 music_chunk *GetNextChunk() const;
+
+ bool PlayChunk(const music_chunk *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..6cd2c9fa6
--- /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(-1)
+{
+}
+
+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(music_chunk *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 struct music_chunk *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 music_chunk *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 struct music_chunk *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 struct music_chunk *chunk;
+ bool is_tail;
+ struct music_chunk *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->times >= 0.0)
+ /* only update elapsed_time if the chunk
+ provides a defined value */
+ 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 */
+ 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 = -1.0;
+}
+
+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 = -1.0;
+}
+
+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 = -1.0;
+}
+
+void
+MultipleOutputs::SongBorder()
+{
+ /* clear the elapsed_time pointer at the beginning of a new
+ song */
+ elapsed_time = 0.0;
+}
diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx
new file mode 100644
index 000000000..296b9815e
--- /dev/null
+++ b/src/output/MultipleOutputs.hxx
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 "Compiler.h"
+
+#include <vector>
+
+#include <assert.h>
+
+struct AudioFormat;
+class MusicBuffer;
+class MusicPipe;
+class EventLoop;
+class MixerListener;
+struct music_chunk;
+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.
+ */
+ float 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 #music_chunk 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 #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 Play(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 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 #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 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
+ float 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 music_chunk *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 struct music_chunk *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 68f2a38aa..000000000
--- a/src/output/OssOutputPlugin.cxx
+++ /dev/null
@@ -1,776 +0,0 @@
-/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this 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;
-
- /* reopen the device since it was closed by dropBufferedAudio */
- if (od->fd < 0 && !oss_reopen(od, error))
- return 0;
-
-#ifdef AFMT_S24_PACKED
- chunk = od->pcm_export->Export(chunk, size, size);
-#endif
-
- while (true) {
- ret = write(od->fd, chunk, size);
- if (ret > 0) {
-#ifdef AFMT_S24_PACKED
- ret = od->pcm_export->CalcSourceSize(ret);
-#endif
- return ret;
- }
-
- if (ret < 0 && errno != EINTR) {
- 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..7f3a8da7b
--- /dev/null
+++ b/src/output/OutputState.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.
+ */
+
+/*
+ * 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 "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(FILE *fp, const MultipleOutputs &outputs)
+{
+ for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
+ const AudioOutput &ao = outputs.Get(i);
+
+ fprintf(fp, 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..32f4bbcce
--- /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
+
+#include <stdio.h>
+
+class MultipleOutputs;
+
+bool
+audio_output_state_read(const char *line, MultipleOutputs &outputs);
+
+void
+audio_output_state_save(FILE *fp, 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..a7fb2b87f
--- /dev/null
+++ b/src/output/OutputThread.cxx
@@ -0,0 +1,678 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "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 "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();
+ 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);
+
+ CloseFilter();
+ 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);
+
+ CloseFilter();
+ 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;
+
+ CloseFilter();
+ 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 const void *
+ao_chunk_data(AudioOutput *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(AudioOutput *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;
+
+ void *dest = ao->cross_fade_buffer.Get(other_length);
+ memcpy(dest, other_data, other_length);
+ if (!pcm_mix(ao->cross_fade_dither, dest, data, length,
+ ao->in_audio_format.format,
+ 1.0 - chunk->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;
+}
+
+inline bool
+AudioOutput::PlayChunk(const music_chunk *chunk)
+{
+ assert(filter != nullptr);
+
+ if (tags && gcc_unlikely(chunk->tag != nullptr)) {
+ mutex.unlock();
+ ao_plugin_send_tag(this, chunk->tag);
+ 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(this, chunk, &size);
+ if (data == nullptr) {
+ Close(false);
+
+ /* don't automatically reopen this device for 10
+ seconds */
+ fail_timer.Update();
+ return false;
+ }
+
+ Error error;
+
+ while (size > 0 && command == AO_COMMAND_NONE) {
+ size_t nbytes;
+
+ if (!WaitForDelay())
+ break;
+
+ mutex.unlock();
+ nbytes = ao_plugin_play(this, 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 <= size);
+ assert(nbytes % out_audio_format.GetFrameSize() == 0);
+
+ data += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+inline const music_chunk *
+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 music_chunk *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 895a165d1..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;
- volatile 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 19f2b61cd..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 nullptr;
- }
-
- const char *host = require_block_string(param, "host");
- const char *mount = require_block_string(param, "mount");
- unsigned port = param.GetBlockValue("port", 0u);
- if (port == 0) {
- 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..f798bac63
--- /dev/null
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "util/Manual.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#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 {
+ 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 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).
+ */
+ uint8_t *silence;
+
+ AlsaOutput()
+ :base(alsa_output_plugin),
+ mode(0), writei(snd_pcm_writei) {
+ }
+
+ bool Init(const config_param &param, Error &error) {
+ return base.Configure(param, 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();
+}
+
+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 AudioOutput *
+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(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(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 = 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_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));
+ delete[] 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(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_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(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 % 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;
+ }
+ }
+
+ chunk = ad->pcm_export->Export(chunk, size, size);
+
+ assert(size % ad->out_frame_size == 0);
+
+ size /= ad->out_frame_size;
+
+ while (true) {
+ snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
+
+ size_t bytes_written = ret * ad->out_frame_size;
+ return ad->pcm_export->CalcSourceSize(bytes_written);
+ }
+
+ if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
+ alsa_recover(ad, ret) < 0) {
+ 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..05b14b29f
--- /dev/null
+++ b/src/output/plugins/OssOutputPlugin.cxx
@@ -0,0 +1,772 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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/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;
+
+ /* reopen the device since it was closed by dropBufferedAudio */
+ if (od->fd < 0 && !oss_reopen(od, error))
+ return 0;
+
+#ifdef AFMT_S24_PACKED
+ chunk = od->pcm_export->Export(chunk, size, size);
+#endif
+
+ while (true) {
+ ret = write(od->fd, chunk, size);
+ if (ret > 0) {
+#ifdef AFMT_S24_PACKED
+ ret = od->pcm_export->CalcSourceSize(ret);
+#endif
+ return ret;
+ }
+
+ if (ret < 0 && errno != EINTR) {
+ 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..ec3725a71
--- /dev/null
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -0,0 +1,885 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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)
+{
+ 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 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..ac4dd8a4e
--- /dev/null
+++ b/src/output/plugins/RoarOutputPlugin.cxx
@@ -0,0 +1,423 @@
+/*
+ * 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;
+ volatile 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";
+
+ 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(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..a16c8fecd
--- /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 nullptr;
+ }
+
+ const char *host = require_block_string(param, "host");
+ const char *mount = require_block_string(param, "mount");
+ unsigned port = param.GetBlockValue("port", 0u);
+ if (port == 0) {
+ 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 (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(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..a16c60bc3
--- /dev/null
+++ b/src/output/plugins/httpd/HttpdInternal.hxx
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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"
+
+#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 GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winvalid-offsetof"
+#endif
+
+ static constexpr HttpdOutput *Cast(AudioOutput *ao) {
+ return ContainerCast(ao, HttpdOutput, base);
+ }
+
+#if GCC_CHECK_VERSION(4,6) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ using DeferredMonitor::GetEventLoop;
+
+ bool Init(const config_param &param, Error &error);
+
+ 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..f93f4f677
--- /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..0b7f9d57b
--- /dev/null
+++ b/src/pcm/FallbackResampler.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_PCM_FALLBACK_RESAMPLER_HXX
+#define MPD_PCM_FALLBACK_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+/**
+ * A naive resampler that is used when no external library was found
+ * (or when the user explicitly asks for bad quality).
+ */
+class FallbackPcmResampler final : public PcmResampler {
+ AudioFormat format;
+ unsigned out_rate;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+};
+
+#endif
diff --git a/src/pcm/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..64e2d8594
--- /dev/null
+++ b/src/pcm/FormatConverter.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 "FormatConverter.hxx"
+#include "PcmFormat.hxx"
+#include "Domain.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+
+bool
+PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format,
+ gcc_unused Error &error)
+{
+ assert(_src_format != SampleFormat::UNDEFINED);
+ assert(_dest_format != SampleFormat::UNDEFINED);
+
+ src_format = _src_format;
+ dest_format = _dest_format;
+ return true;
+}
+
+void
+PcmFormatConverter::Close()
+{
+#ifndef NDEBUG
+ src_format = SampleFormat::UNDEFINED;
+ dest_format = SampleFormat::UNDEFINED;
+#endif
+}
+
+ConstBuffer<void>
+PcmFormatConverter::Convert(ConstBuffer<void> src, Error &error)
+{
+ switch (dest_format) {
+ case SampleFormat::UNDEFINED:
+ assert(false);
+ gcc_unreachable();
+
+ case SampleFormat::S8:
+ case SampleFormat::DSD:
+ error.Format(pcm_domain,
+ "PCM conversion from %s to %s is not implemented",
+ sample_format_to_string(src_format),
+ sample_format_to_string(dest_format));
+ return nullptr;
+
+ case SampleFormat::S16:
+ return pcm_convert_to_16(buffer, dither,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::S24_P32:
+ return pcm_convert_to_24(buffer,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::S32:
+ return pcm_convert_to_32(buffer,
+ src_format,
+ src).ToVoid();
+
+ case SampleFormat::FLOAT:
+ return pcm_convert_to_float(buffer,
+ src_format,
+ src).ToVoid();
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
diff --git a/src/pcm/FormatConverter.hxx b/src/pcm/FormatConverter.hxx
new file mode 100644
index 000000000..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..86d74d95a
--- /dev/null
+++ b/src/pcm/LibsamplerateResampler.hxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX
+#define MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+
+#include <samplerate.h>
+
+/**
+ * A resampler using libsamplerate.
+ */
+class LibsampleratePcmResampler final : public PcmResampler {
+ unsigned src_rate, dest_rate;
+ unsigned channels;
+
+ SRC_STATE *state;
+ SRC_DATA data;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate,
+ Error &error) override;
+ virtual void Close() override;
+ virtual ConstBuffer<void> Resample(ConstBuffer<void> src,
+ Error &error) override;
+
+private:
+ ConstBuffer<float> Resample2(ConstBuffer<float> src, Error &error);
+};
+
+bool
+pcm_resample_lsr_global_init(const char *converter, Error &error);
+
+#endif
diff --git a/src/pcm/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..ad79e60fc 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,151 @@
#include "config.h"
#include "PcmConvert.hxx"
-#include "PcmChannels.hxx"
-#include "PcmFormat.hxx"
+#include "Domain.hxx"
+#include "ConfiguredResampler.hxx"
#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
+#include "util/ConstBuffer.hxx"
#include <assert.h>
#include <math.h>
-const Domain pcm_convert_domain("pcm_convert");
+bool
+pcm_convert_global_init(Error &error)
+{
+ return pcm_resampler_global_init(error);
+}
PcmConvert::PcmConvert()
{
+#ifndef NDEBUG
+ src_format.Clear();
+ dest_format.Clear();
+#endif
}
PcmConvert::~PcmConvert()
{
+ assert(!src_format.IsValid());
+ assert(!dest_format.IsValid());
}
-void
-PcmConvert::Reset()
+bool
+PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format,
+ Error &error)
{
- dsd.Reset();
- resampler.Reset();
-}
+ assert(!src_format.IsValid());
+ assert(!dest_format.IsValid());
+ assert(_src_format.IsValid());
+ assert(_dest_format.IsValid());
-inline const int16_t *
-PcmConvert::Convert16(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
-{
- const int16_t *buf;
- size_t len;
+ src_format = _src_format;
+ dest_format = _dest_format;
+
+ AudioFormat format = src_format;
+ if (format.format == SampleFormat::DSD)
+ format.format = SampleFormat::FLOAT;
- assert(dest_format.format == SampleFormat::S16);
+ enable_resampler = format.sample_rate != dest_format.sample_rate;
+ if (enable_resampler) {
+ if (!resampler.Open(format, dest_format.sample_rate, error))
+ return false;
- buf = pcm_convert_to_16(format_buffer, dither,
- src_format.format,
- src_buffer, src_size,
- &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 16 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
+ format.format = resampler.GetOutputSampleFormat();
+ format.sample_rate = dest_format.sample_rate;
}
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_16(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
- return nullptr;
- }
+ enable_format = format.format != dest_format.format;
+ if (enable_format &&
+ !format_converter.Open(format.format, dest_format.format, error)) {
+ if (enable_resampler)
+ resampler.Close();
+ return false;
}
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample16(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return nullptr;
+ format.format = dest_format.format;
+
+ enable_channels = format.channels != dest_format.channels;
+ if (enable_channels &&
+ !channels_converter.Open(format.format, format.channels,
+ dest_format.channels, error)) {
+ if (enable_format)
+ format_converter.Close();
+ if (enable_resampler)
+ resampler.Close();
+ return false;
}
- *dest_size_r = len;
- return buf;
+ return true;
}
-inline const int32_t *
-PcmConvert::Convert24(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+void
+PcmConvert::Close()
{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format.format == SampleFormat::S24_P32);
-
- buf = pcm_convert_to_24(format_buffer,
- src_format.format,
- src_buffer, src_size, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 24 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
+ if (enable_channels)
+ channels_converter.Close();
+ if (enable_format)
+ format_converter.Close();
+ if (enable_resampler)
+ resampler.Close();
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_24(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
- return nullptr;
- }
- }
-
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample24(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return nullptr;
- }
+ dsd.Reset();
- *dest_size_r = len;
- return buf;
+#ifndef NDEBUG
+ src_format.Clear();
+ dest_format.Clear();
+#endif
}
-inline const int32_t *
-PcmConvert::Convert32(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
+const void *
+PcmConvert::Convert(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ Error &error)
{
- const int32_t *buf;
- size_t len;
-
- assert(dest_format.format == SampleFormat::S32);
-
- buf = pcm_convert_to_32(format_buffer,
- src_format.format,
- src_buffer, src_size, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to 32 bit is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
- }
-
- if (src_format.channels != dest_format.channels) {
- buf = pcm_convert_channels_32(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buf, len, &len);
- if (buf == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
+ ConstBuffer<void> buffer(src, src_size);
+ AudioFormat format = src_format;
+
+ if (format.format == SampleFormat::DSD) {
+ auto s = ConstBuffer<uint8_t>::FromVoid(buffer);
+ auto d = dsd.ToFloat(format.channels,
+ false, s);
+ if (d.IsNull()) {
+ error.Set(pcm_domain,
+ "DSD to PCM conversion failed");
return nullptr;
}
- }
- if (src_format.sample_rate != dest_format.sample_rate) {
- buf = resampler.Resample32(dest_format.channels,
- src_format.sample_rate, buf, len,
- dest_format.sample_rate, &len,
- error);
- if (buf == nullptr)
- return buf;
+ buffer = d.ToVoid();
+ format.format = SampleFormat::FLOAT;
}
- *dest_size_r = len;
- return buf;
-}
-
-inline const float *
-PcmConvert::ConvertFloat(const AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- const AudioFormat dest_format, size_t *dest_size_r,
- Error &error)
-{
- const float *buffer = (const float *)src_buffer;
- size_t size = src_size;
-
- assert(dest_format.format == SampleFormat::FLOAT);
-
- /* convert to float now */
+ if (enable_resampler) {
+ buffer = resampler.Resample(buffer, error);
+ if (buffer.IsNull())
+ return nullptr;
- buffer = pcm_convert_to_float(format_buffer,
- src_format.format,
- buffer, size, &size);
- if (buffer == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %s to float is not implemented",
- sample_format_to_string(src_format.format));
- return nullptr;
+ format.format = resampler.GetOutputSampleFormat();
+ format.sample_rate = dest_format.sample_rate;
}
- /* convert channels */
-
- if (src_format.channels != dest_format.channels) {
- buffer = pcm_convert_channels_float(channels_buffer,
- dest_format.channels,
- src_format.channels,
- buffer, size, &size);
- if (buffer == nullptr) {
- error.Format(pcm_convert_domain,
- "Conversion from %u to %u channels "
- "is not implemented",
- src_format.channels,
- dest_format.channels);
+ if (enable_format) {
+ buffer = format_converter.Convert(buffer, error);
+ if (buffer.IsNull())
return nullptr;
- }
- }
- /* resample with float, because this is the best format for
- libsamplerate */
-
- if (src_format.sample_rate != dest_format.sample_rate) {
- buffer = resampler.ResampleFloat(dest_format.channels,
- src_format.sample_rate,
- buffer, size,
- dest_format.sample_rate,
- &size, error);
- if (buffer == nullptr)
- return nullptr;
+ format.format = dest_format.format;
}
- *dest_size_r = size;
- return buffer;
-}
-
-const void *
-PcmConvert::Convert(AudioFormat src_format,
- const void *src, size_t src_size,
- const AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error)
-{
- AudioFormat float_format;
- if (src_format.format == SampleFormat::DSD) {
- size_t f_size;
- const float *f = dsd.ToFloat(src_format.channels,
- false, (const uint8_t *)src,
- src_size, &f_size);
- if (f == nullptr) {
- error.Set(pcm_convert_domain,
- "DSD to PCM conversion failed");
+ if (enable_channels) {
+ buffer = channels_converter.Convert(buffer, error);
+ if (buffer.IsNull())
return nullptr;
- }
- float_format = src_format;
- float_format.format = SampleFormat::FLOAT;
-
- src_format = float_format;
- src = f;
- src_size = f_size;
+ format.channels = dest_format.channels;
}
- switch (dest_format.format) {
- case SampleFormat::S16:
- return Convert16(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::S24_P32:
- return Convert24(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::S32:
- return Convert32(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- case SampleFormat::FLOAT:
- return ConvertFloat(src_format, src, src_size,
- dest_format, dest_size_r,
- error);
-
- default:
- error.Format(pcm_convert_domain,
- "PCM conversion to %s is not implemented",
- sample_format_to_string(dest_format.format));
- return nullptr;
- }
+ *dest_size_r = buffer.size;
+ return buffer.data;
}
diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx
index 40f785179..7d05b3bb2 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,18 @@
#ifndef PCM_CONVERT_HXX
#define PCM_CONVERT_HXX
-#include "PcmDither.hxx"
#include "PcmDsd.hxx"
-#include "PcmResample.hxx"
#include "PcmBuffer.hxx"
+#include "FormatConverter.hxx"
+#include "ChannelsConverter.hxx"
+#include "GlueResampler.hxx"
+#include "AudioFormat.hxx"
#include <stddef.h>
-struct AudioFormat;
+template<typename T> struct ConstBuffer;
class Error;
+class Domain;
/**
* This object is statically allocated (within another struct), and
@@ -38,27 +41,29 @@ class Error;
class PcmConvert {
PcmDsd dsd;
- PcmResampler resampler;
+ GluePcmResampler resampler;
+ PcmFormatConverter format_converter;
+ PcmChannelsConverter channels_converter;
- PcmDither dither;
+ AudioFormat src_format, dest_format;
- /** the buffer for converting the sample format */
- PcmBuffer format_buffer;
-
- /** the buffer for converting the channel count */
- PcmBuffer channels_buffer;
+ bool enable_resampler, enable_format, enable_channels;
public:
PcmConvert();
~PcmConvert();
+ /**
+ * Prepare the object. Call Close() when done.
+ */
+ bool Open(AudioFormat _src_format, AudioFormat _dest_format,
+ Error &error);
/**
- * Reset the pcm_convert_state object. Use this at the
- * boundary between two distinct songs and each time the
- * format changes.
+ * Close the object after it was prepared with Open(). After
+ * that, it may be reused by calling Open() again.
*/
- void Reset();
+ void Close();
/**
* Converts PCM data between two audio formats.
@@ -72,38 +77,12 @@ public:
* ignore errors
* @return the destination buffer, or NULL on error
*/
- const void *Convert(AudioFormat src_format,
- const void *src, size_t src_size,
- AudioFormat dest_format,
+ const void *Convert(const void *src, size_t src_size,
size_t *dest_size_r,
Error &error);
-
-private:
- const int16_t *Convert16(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const int32_t *Convert24(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const int32_t *Convert32(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
-
- const float *ConvertFloat(AudioFormat src_format,
- const void *src_buffer, size_t src_size,
- AudioFormat dest_format,
- size_t *dest_size_r,
- Error &error);
};
-extern const class Domain pcm_convert_domain;
+bool
+pcm_convert_global_init(Error &error);
#endif
diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx
index 98d0d443e..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/PcmDsd.cxx b/src/pcm/PcmDsd.cxx
index 4db274635..ee549658d 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,20 @@ PcmDsd::Reset()
dsd2pcm_reset(dsd2pcm[i]);
}
-const float *
+ConstBuffer<float>
PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
- const uint8_t *src, size_t src_size,
- size_t *dest_size_r)
+ ConstBuffer<uint8_t> src)
{
- assert(src != nullptr);
- assert(src_size > 0);
- assert(src_size % channels == 0);
+ assert(!src.IsNull());
+ assert(!src.IsEmpty());
+ assert(src.size % channels == 0);
assert(channels <= ARRAY_SIZE(dsd2pcm));
- const unsigned num_samples = src_size;
- const unsigned num_frames = src_size / channels;
+ const unsigned num_samples = src.size;
+ const unsigned num_frames = src.size / channels;
float *dest;
const size_t dest_size = num_samples * sizeof(*dest);
- *dest_size_r = dest_size;
dest = (float *)buffer.Get(dest_size);
for (unsigned c = 0; c < channels; ++c) {
@@ -73,9 +71,9 @@ PcmDsd::ToFloat(unsigned channels, bool lsbfirst,
}
dsd2pcm_translate(dsd2pcm[c], num_frames,
- src + c, channels,
+ src.data + c, channels,
lsbfirst, dest + c, channels);
}
- return dest;
+ return { dest, num_samples };
}
diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx
index 26ee11b13..cb3ef1fd6 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, bool lsbfirst,
+ ConstBuffer<uint8_t> src);
};
#endif
diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx
index 2d0f33a15..1bfb4206f 100644
--- a/src/pcm/PcmDsdUsb.cxx
+++ b/src/pcm/PcmDsdUsb.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,8 @@
#include "PcmBuffer.hxx"
#include "AudioFormat.hxx"
+#include <assert.h>
+
constexpr
static inline uint32_t
pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b)
diff --git a/src/pcm/PcmDsdUsb.hxx b/src/pcm/PcmDsdUsb.hxx
index 3b7121465..bb31d2d45 100644
--- a/src/pcm/PcmDsdUsb.hxx
+++ b/src/pcm/PcmDsdUsb.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/PcmExport.cxx b/src/pcm/PcmExport.cxx
index f6ce1e661..fc3ef57ca 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
diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx
index bd18c0534..61ac14c6f 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
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..2508e816e
--- /dev/null
+++ b/src/pcm/SoxrResampler.cxx
@@ -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.
+ */
+
+#include "config.h"
+#include "SoxrResampler.hxx"
+#include "AudioFormat.hxx"
+#include "util/ASCII.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <soxr.h>
+
+#include <assert.h>
+
+static constexpr Domain soxr_domain("soxr");
+
+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;
+
+ const size_t o_frames = size_t(n_frames * ratio + 0.5);
+
+ float *output_buffer = (float *)buffer.Get(o_frames * frame_size);
+
+ size_t i_done, o_done;
+ soxr_error_t e = soxr_process(soxr, src.data, n_frames, &i_done,
+ output_buffer, o_frames, &o_done);
+ if (e != nullptr) {
+ error.Format(soxr_domain, "soxr error: %s", e);
+ return nullptr;
+ }
+
+ return { output_buffer, o_done * frame_size };
+}
diff --git a/src/pcm/SoxrResampler.hxx b/src/pcm/SoxrResampler.hxx
new file mode 100644
index 000000000..efe9bd5cb
--- /dev/null
+++ b/src/pcm/SoxrResampler.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_PCM_SOXR_RESAMPLER_HXX
+#define MPD_PCM_SOXR_RESAMPLER_HXX
+
+#include "Resampler.hxx"
+#include "PcmBuffer.hxx"
+
+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/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 d758650eb..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 = {
- "cue",
-
- nullptr,
- nullptr,
- embcue_playlist_open_uri,
- nullptr,
-
- embcue_playlist_suffixes,
- nullptr,
- 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 8d260fec7..000000000
--- a/src/playlist/ExtM3uPlaylistPlugin.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 "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",
- NULL
-};
-
-static const char *const extm3u_mime_types[] = {
- "audio/x-mpegurl",
- NULL
-};
-
-const struct playlist_plugin extm3u_playlist_plugin = {
- "extm3u",
-
- nullptr,
- nullptr,
- nullptr,
- extm3u_open_stream,
-
- 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 3f99bdfdf..000000000
--- a/src/playlist/M3uPlaylistPlugin.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 "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",
- 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..e87a4f6dd
--- /dev/null
+++ b/src/playlist/MemorySongEnumerator.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_MEMORY_PLAYLIST_PROVIDER_HXX
+#define MPD_MEMORY_PLAYLIST_PROVIDER_HXX
+
+#include "SongEnumerator.hxx"
+#include "DetachedSong.hxx"
+
+#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..c50254309
--- /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.c_str(), 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.c_str(), mutex, cond);
+ }
+
+ const auto uri2 = storage->MapUTF8(uri);
+ return playlist_open_remote(uri, 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..bc5932de3
--- /dev/null
+++ b/src/playlist/PlaylistRegistry.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 "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)
+{
+ const char *suffix;
+ SongEnumerator *playlist = nullptr;
+
+ assert(uri != nullptr);
+
+ suffix = uri_get_suffix(uri);
+ 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;
+ }
+
+ const char *suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
+ if (suffix != nullptr) {
+ auto playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != nullptr)
+ 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..3e3b40949
--- /dev/null
+++ b/src/playlist/PlaylistSong.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 "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)
+{
+ {
+ TagBuilder builder(add.GetTag());
+ builder.Complement(base.GetTag());
+ add.SetTag(builder.Commit());
+ }
+
+ add.SetLastModified(base.GetLastModified());
+}
+
+static void
+apply_song_metadata(DetachedSong &dest, const DetachedSong &src)
+{
+ if (!src.GetTag().IsDefined() &&
+ src.GetStartMS() == 0 && src.GetEndMS() == 0)
+ return;
+
+ merge_song_metadata(dest, src);
+
+ if (dest.GetTag().IsDefined() && dest.GetTag().time > 0 &&
+ src.GetStartMS() > 0 && src.GetEndMS() == 0 &&
+ src.GetStartMS() / 1000 < (unsigned)dest.GetTag().time)
+ /* the range is open-ended, and the playlist plugin
+ did not know the total length of the song file
+ (e.g. last track on a CUE file); fix it up here */
+ dest.WritableTag().time =
+ dest.GetTag().time - src.GetStartMS() / 1000;
+}
+
+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());
+
+ apply_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..5855c598b
--- /dev/null
+++ b/src/playlist/PlaylistStream.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 "PlaylistStream.hxx"
+#include "PlaylistRegistry.hxx"
+#include "CloseSongEnumerator.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "input/InputStream.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+
+static SongEnumerator *
+playlist_open_path_suffix(const char *path_fs, Mutex &mutex, Cond &cond)
+{
+ assert(path_fs != nullptr);
+
+ const char *suffix = uri_get_suffix(path_fs);
+ if (suffix == nullptr || !playlist_suffix_supported(suffix))
+ return nullptr;
+
+ Error error;
+ InputStream *is = InputStream::OpenReady(path_fs, mutex, cond, error);
+ if (is == nullptr) {
+ if (error.IsDefined())
+ 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(const char *path_fs, Mutex &mutex, Cond &cond)
+{
+ auto playlist = playlist_list_open_uri(path_fs, mutex, cond);
+ if (playlist == nullptr)
+ playlist = playlist_open_path_suffix(path_fs, 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..02f5485ef
--- /dev/null
+++ b/src/playlist/PlaylistStream.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_PLAYLIST_STREAM_HXX
+#define MPD_PLAYLIST_STREAM_HXX
+
+#include "Compiler.h"
+
+class Mutex;
+class Cond;
+class SongEnumerator;
+
+/**
+ * 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
+ */
+gcc_nonnull_all
+SongEnumerator *
+playlist_open_path(const char *path_fs, 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..0db2a4ab0
--- /dev/null
+++ b/src/playlist/Print.cxx
@@ -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.
+ */
+
+#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)) {
+ if (detail)
+ song_print_info(client, *song);
+ else
+ 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..dc96218f4
--- /dev/null
+++ b/src/playlist/cue/CueParser.cxx
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 = 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(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->GetStartMS() < (unsigned)position_ms) {
+ last_updated = true;
+ previous->SetEndMS(position_ms);
+ previous->WritableTag().time =
+ (previous->GetEndMS() - previous->GetStartMS() + 500) / 1000;
+ }
+
+ current->SetStartMS(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..8776baace
--- /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;
+
+ 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/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..2e903ae03
--- /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 = {
+ "cue",
+
+ nullptr,
+ nullptr,
+ embcue_playlist_open_uri,
+ nullptr,
+
+ embcue_playlist_suffixes,
+ nullptr,
+ 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..a337bce4c
--- /dev/null
+++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx
@@ -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.
+ */
+
+#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() {
+ std::string line;
+ return tis.ReadLine(line) &&
+ strcmp(line.c_str(), "#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 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;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return Tag();
+
+ 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 Tag();
+
+ TagBuilder tag;
+ tag.SetTime(duration);
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag.AddItem(TAG_NAME, name);
+
+ return tag.Commit();
+}
+
+DetachedSong *
+ExtM3uPlaylist::NextSong()
+{
+ Tag tag;
+ std::string line;
+ const char *line_s;
+
+ do {
+ if (!tis.ReadLine(line))
+ return NULL;
+
+ line_s = line.c_str();
+
+ if (StringStartsWith(line_s, "#EXTINF:")) {
+ tag = extm3u_parse_tag(line_s + 8);
+ continue;
+ }
+
+ line_s = strchug_fast(line_s);
+ } while (line_s[0] == '#' || *line_s == 0);
+
+ return new DetachedSong(line_s, std::move(tag));
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ "extm3u",
+
+ nullptr,
+ nullptr,
+ nullptr,
+ extm3u_open_stream,
+
+ 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..f892f2010
--- /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()
+{
+ 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 new DetachedSong(line_s);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ 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..7df7d134b
--- /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.SetTime(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..d9f62300b
--- /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.SetTime(data->duration / 1000);
+ 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 b13ea3f4e..d2c40bbd6 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
diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx
index ea28de79e..f7f9531d0 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
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..abcb2ceaa
--- /dev/null
+++ b/src/queue/Playlist.cxx
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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;
+
+ 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 = GetCurrentPosition();
+
+ queue.ShuffleOrder();
+
+ if (current_position >= 0) {
+ /* make sure the current song is the first in
+ the order list, so the whole rest of the
+ playlist is played after that */
+ unsigned current_order =
+ queue.PositionToOrder(current_position);
+ queue.SwapOrders(0, current_order);
+ current = 0;
+ } else
+ current = -1;
+ } else
+ playlist_order(*this);
+
+ UpdateQueuedSong(pc, queued_song);
+
+ idle_add(IDLE_OPTIONS);
+}
+
+int
+playlist::GetCurrentPosition() const
+{
+ return current >= 0
+ ? queue.OrderToPosition(current)
+ : -1;
+}
+
+int
+playlist::GetNextPosition() const
+{
+ if (current < 0)
+ return -1;
+
+ if (queue.single && queue.repeat)
+ return queue.OrderToPosition(current);
+ else if (queue.IsValidOrder(current + 1))
+ return queue.OrderToPosition(current + 1);
+ else if (queue.repeat)
+ return queue.OrderToPosition(0);
+
+ return -1;
+}
diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx
new file mode 100644
index 000000000..4a4c7f30c
--- /dev/null
+++ b/src/queue/Playlist.hxx
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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;
+
+struct playlist {
+ /**
+ * The song queue - it contains the "real" playlist.
+ */
+ struct Queue queue;
+
+ /**
+ * This value is true if the player is currently playing (or
+ * should be playing).
+ */
+ bool playing;
+
+ /**
+ * If true, then any error is fatal; if false, MPD will
+ * attempt to play the next song on non-fatal errors. During
+ * seeking, this flag is set.
+ */
+ bool stop_on_error;
+
+ /**
+ * Number of errors since playback was started. If this
+ * number exceeds the length of the playlist, MPD gives up,
+ * because all songs have been tried.
+ */
+ unsigned error_count;
+
+ /**
+ * The "current song pointer". This is the song which is
+ * played when we get the "play" command. It is also the song
+ * which is currently being played.
+ */
+ int current;
+
+ /**
+ * The "next" song to be played, when the current one
+ * finishes. The decoder thread may start decoding and
+ * buffering it, while the "current" song is still playing.
+ *
+ * This variable is only valid if #playing is true.
+ */
+ int queued;
+
+ playlist(unsigned max_length)
+ :queue(max_length), playing(false), current(-1), queued(-1) {
+ }
+
+ ~playlist() {
+ }
+
+ uint32_t GetVersion() const {
+ return queue.version;
+ }
+
+ unsigned GetLength() const {
+ return queue.GetLength();
+ }
+
+ unsigned PositionToId(unsigned position) const {
+ return queue.PositionToId(position);
+ }
+
+ gcc_pure
+ int GetCurrentPosition() const;
+
+ gcc_pure
+ int GetNextPosition() const;
+
+ /**
+ * Returns the song object which is currently queued. Returns
+ * none if there is none (yet?) or if MPD isn't playing.
+ */
+ gcc_pure
+ const 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 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);
+
+ 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 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/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx
new file mode 100644
index 000000000..9d75cc26d
--- /dev/null
+++ b/src/queue/PlaylistControl.cxx
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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 playlist_play_order() will
+ discard them anyway */
+ }
+
+ PlayOrder(pc, next_order);
+ }
+
+ /* Consume mode removes each played songs. */
+ if (queue.consume)
+ DeleteOrder(pc, old_current);
+}
+
+void
+playlist::PlayPrevious(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::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
+{
+ if (!queue.IsValidPosition(song))
+ return PlaylistResult::BAD_RANGE;
+
+ const DetachedSong *queued_song = GetQueuedSong();
+
+ unsigned i = queue.random
+ ? queue.PositionToOrder(song)
+ : song;
+
+ pc.ClearError();
+ stop_on_error = true;
+ error_count = 0;
+
+ if (!playing || (unsigned)current != i) {
+ /* seeking is not within the current song - prepare
+ song change */
+
+ playing = true;
+ current = i;
+
+ queued_song = nullptr;
+ }
+
+ 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::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 SeekSongPosition(pc, current, seek_time);
+}
diff --git a/src/queue/PlaylistEdit.cxx b/src/queue/PlaylistEdit.cxx
new file mode 100644
index 000000000..e8f1a178f
--- /dev/null
+++ b/src/queue/PlaylistEdit.cxx
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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()
+{
+ queue.IncrementVersion();
+
+ idle_add(IDLE_PLAYLIST);
+}
+
+void
+playlist::Clear(PlayerControl &pc)
+{
+ Stop(pc);
+
+ queue.Clear();
+ current = -1;
+
+ 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 remaning
+ 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();
+}
diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx
new file mode 100644
index 000000000..f5c798e3e
--- /dev/null
+++ b/src/queue/PlaylistState.cxx
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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/TextFile.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(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, 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;
+ int seek_time = 0;
+ 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)) {
+ seek_time =
+ atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)]));
+ } 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 == 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/queue/PlaylistState.hxx b/src/queue/PlaylistState.hxx
new file mode 100644
index 000000000..8d3f88ae2
--- /dev/null
+++ b/src/queue/PlaylistState.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.
+ */
+
+/*
+ * 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;
+class SongLoader;
+
+void
+playlist_state_save(FILE *fp, 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..28fc361d7
--- /dev/null
+++ b/src/queue/QueueSave.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"
+#include "QueueSave.hxx"
+#include "Queue.hxx"
+#include "PlaylistError.hxx"
+#include "DetachedSong.hxx"
+#include "SongSave.hxx"
+#include "SongLoader.hxx"
+#include "playlist/PlaylistSong.hxx"
+#include "fs/TextFile.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(FILE *fp, int idx, const DetachedSong &song)
+{
+ fprintf(fp, "%i:%s\n", idx, song.GetURI());
+}
+
+static void
+queue_save_full_song(FILE *fp, const DetachedSong &song)
+{
+ song_save(fp, song);
+}
+
+static void
+queue_save_song(FILE *fp, int idx, const DetachedSong &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 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..470823a24
--- /dev/null
+++ b/src/queue/QueueSave.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.
+ */
+
+/*
+ * 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;
+class SongLoader;
+
+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 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..ac6fcecd9
--- /dev/null
+++ b/src/storage/CompositeStorage.cxx
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <set>
+
+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 */
+ virtual const char *Read() override;
+ virtual 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..3daa45ee3
--- /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 */
+ virtual bool GetInfo(const char *uri, bool follow, FileInfo &info,
+ Error &error) override;
+
+ virtual StorageDirectoryReader *OpenDirectory(const char *uri,
+ Error &error) override;
+
+ virtual std::string MapUTF8(const char *uri) const override;
+
+ virtual AllocatedPath MapFS(const char *uri) const override;
+
+ virtual 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..800a18ba0
--- /dev/null
+++ b/src/storage/Configured.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 "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(const char *uri, Error &error)
+{
+ Storage *storage = CreateStorageURI(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(Error &error)
+{
+ assert(!error.IsDefined());
+
+ auto uri = config_get_string(CONF_MUSIC_DIR, nullptr);
+ if (uri != nullptr && uri_has_scheme(uri))
+ return CreateConfiguredStorageUri(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..d78857a26
--- /dev/null
+++ b/src/storage/Configured.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_STORAGE_CONFIG_HXX
+#define MPD_STORAGE_CONFIG_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+class Error;
+class Storage;
+
+/**
+ * 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(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/Registry.cxx b/src/storage/Registry.cxx
new file mode 100644
index 000000000..b3fdd1642
--- /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(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(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..9696b3de1
--- /dev/null
+++ b/src/storage/Registry.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_STORAGE_REGISTRY_HXX
+#define MPD_STORAGE_REGISTRY_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+struct StoragePlugin;
+class Storage;
+class Error;
+
+/**
+ * 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(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..892e8e43b
--- /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 "fs/AllocatedPath.hxx"
+#include "fs/DirectoryReader.hxx"
+
+#include <string>
+
+struct FileInfo;
+class AllocatedPath;
+
+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..d91caf24b
--- /dev/null
+++ b/src/storage/StoragePlugin.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_STORAGE_PLUGIN_HXX
+#define MPD_STORAGE_PLUGIN_HXX
+
+#include "check.h"
+
+class Error;
+class Storage;
+
+struct StoragePlugin {
+ const char *name;
+
+ Storage *(*create_uri)(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..34d11569c
--- /dev/null
+++ b/src/storage/plugins/LocalStorage.cxx
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 */
+ virtual const char *Read() override;
+ virtual 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 */
+ virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) override;
+
+ virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) override;
+
+ virtual std::string MapUTF8(const char *uri_utf8) const override;
+
+ virtual AllocatedPath MapFS(const char *uri_utf8) const override;
+
+ virtual 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..72c138feb
--- /dev/null
+++ b/src/storage/plugins/NfsStorage.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.
+ */
+
+#include "config.h"
+#include "NfsStorage.hxx"
+#include "storage/StoragePlugin.hxx"
+#include "storage/StorageInterface.hxx"
+#include "storage/FileInfo.hxx"
+#include "lib/nfs/Domain.hxx"
+#include "util/Error.hxx"
+#include "thread/Mutex.hxx"
+
+extern "C" {
+#include <nfsc/libnfs.h>
+#include <nfsc/libnfs-raw-nfs.h>
+}
+
+#include <sys/stat.h>
+#include <fcntl.h>
+
+class NfsDirectoryReader final : public StorageDirectoryReader {
+ const std::string base;
+
+ nfs_context *ctx;
+ nfsdir *dir;
+
+ nfsdirent *ent;
+
+public:
+ NfsDirectoryReader(const char *_base, nfs_context *_ctx, nfsdir *_dir)
+ :base(_base), ctx(_ctx), dir(_dir) {}
+
+ virtual ~NfsDirectoryReader();
+
+ /* virtual methods from class StorageDirectoryReader */
+ virtual const char *Read() override;
+ virtual bool GetInfo(bool follow, FileInfo &info,
+ Error &error) override;
+};
+
+class NfsStorage final : public Storage {
+ const std::string base;
+
+ nfs_context *ctx;
+
+public:
+ NfsStorage(const char *_base, nfs_context *_ctx)
+ :base(_base), ctx(_ctx) {}
+
+ virtual ~NfsStorage() {
+ nfs_destroy_context(ctx);
+ }
+
+ /* virtual methods from class Storage */
+ virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) override;
+
+ virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) override;
+
+ virtual std::string MapUTF8(const char *uri_utf8) const override;
+
+ virtual const char *MapToRelativeUTF8(const char *uri_utf8) const override;
+};
+
+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 bool
+GetInfo(nfs_context *ctx, const char *path, FileInfo &info, Error &error)
+{
+ struct stat st;
+ int result = nfs_stat(ctx, path, &st);
+ if (result < 0) {
+ error.SetErrno(-result, "nfs_stat() failed");
+ 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
+NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow,
+ FileInfo &info, Error &error)
+{
+ /* libnfs paths must begin with a slash */
+ std::string path(uri_utf8);
+ path.insert(path.begin(), '/');
+
+ return ::GetInfo(ctx, path.c_str(), info, error);
+}
+
+StorageDirectoryReader *
+NfsStorage::OpenDirectory(const char *uri_utf8, Error &error)
+{
+ /* libnfs paths must begin with a slash */
+ std::string path(uri_utf8);
+ path.insert(path.begin(), '/');
+
+ nfsdir *dir;
+ int result = nfs_opendir(ctx, path.c_str(), &dir);
+ if (result < 0) {
+ error.SetErrno(-result, "nfs_opendir() failed");
+ return nullptr;
+ }
+
+ return new NfsDirectoryReader(uri_utf8, ctx, dir);
+}
+
+gcc_pure
+static bool
+SkipNameFS(const char *name)
+{
+ return name[0] == '.' &&
+ (name[1] == 0 ||
+ (name[1] == '.' && name[2] == 0));
+}
+
+NfsDirectoryReader::~NfsDirectoryReader()
+{
+ nfs_closedir(ctx, dir);
+}
+
+const char *
+NfsDirectoryReader::Read()
+{
+ while ((ent = nfs_readdir(ctx, dir)) != nullptr) {
+ if (!SkipNameFS(ent->name))
+ return ent->name;
+ }
+
+ return nullptr;
+}
+
+bool
+NfsDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info,
+ gcc_unused Error &error)
+{
+ assert(ent != nullptr);
+
+ 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;
+ return true;
+}
+
+static Storage *
+CreateNfsStorageURI(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_context *ctx = nfs_init_context();
+ if (ctx == nullptr) {
+ error.Set(nfs_domain, "nfs_init_context() failed");
+ return nullptr;
+ }
+
+ int result = nfs_mount(ctx, server.c_str(), mount);
+ if (result < 0) {
+ nfs_destroy_context(ctx);
+ error.SetErrno(-result, "nfs_mount() failed");
+ return nullptr;
+ }
+
+ return new NfsStorage(base, ctx);
+}
+
+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..a73c8d65c
--- /dev/null
+++ b/src/storage/plugins/SmbclientStorage.cxx
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this 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 "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 */
+ virtual const char *Read() override;
+ virtual 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 */
+ virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info,
+ Error &error) override;
+
+ virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8,
+ Error &error) override;
+
+ virtual std::string MapUTF8(const char *uri_utf8) const override;
+
+ virtual 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(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..910e1629c 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, ':') != NULL) {
+ std::string result("[");
+ result.append(host);
+ result.append("]:");
+ result.append(serv);
+ return result;
+ }
#endif
- return g_strconcat(host, ":", serv, NULL);
+ std::string result(host);
+ result.push_back(':');
+ result.append(serv);
+ return result;
}
struct addrinfo *
@@ -98,19 +105,19 @@ resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
Error &error)
{
- char *p = g_strdup(host_port);
- const char *host = p, *port = NULL;
+ std::string p(host_port);
+ const char *host = p.c_str(), *port = nullptr;
if (host_port[0] == '[') {
/* IPv6 needs enclosing square braces, to
differentiate between IP colons and the port
separator */
- char *q = strchr(p + 1, ']');
- if (q != NULL && q[1] == ':' && q[2] != 0) {
- *q = 0;
+ size_t q = p.find(']', 1);
+ if (q != p.npos && p[q + 1] == ':' && p[q + 2] != 0) {
+ p[q] = 0;
+ port = host + q + 2;
++host;
- port = q + 2;
}
}
@@ -118,10 +125,11 @@ resolve_host_port(const char *host_port, unsigned default_port,
/* port is after the colon, but only if it's the only
colon (don't split IPv6 addresses) */
- char *q = strchr(p, ':');
- if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) {
- *q = 0;
- port = q + 1;
+ auto q = p.find(':');
+ if (q != p.npos && p[q + 1] != 0 &&
+ p.find(':', q + 1) == p.npos) {
+ p[q] = 0;
+ port = host + q + 1;
}
}
@@ -142,7 +150,6 @@ resolve_host_port(const char *host_port, unsigned default_port,
struct addrinfo *ai;
int ret = getaddrinfo(host, port, &hints, &ai);
- g_free(p);
if (ret != 0) {
error.Format(resolver_domain, ret,
"Failed to look up '%s': %s",
diff --git a/src/system/Resolver.hxx b/src/system/Resolver.hxx
index 62ef455a1..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..6afb3e96e 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
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/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..59f66bf84
--- /dev/null
+++ b/src/tag/Set.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 "Set.hxx"
+#include "TagBuilder.hxx"
+
+#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;
+ const unsigned n = src.num_items;
+ for (unsigned i = 0; i < n; ++i) {
+ if (src.items[i]->type == src_type) {
+ dest.AddItem(dest_type, src.items[i]->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 (unsigned i = 0; i < tag.num_items; ++i) {
+ if (tag.items[i]->type == src_type) {
+ InsertUnique(tag, dest_type,
+ tag.items[i]->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 ||
+ /* 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..448f3b26a 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,12 +58,6 @@ tag_name_parse_i(const char *name)
return TAG_NUM_OF_ITEM_TYPES;
}
-static size_t
-items_size(const Tag &tag)
-{
- return tag.num_items * sizeof(TagItem *);
-}
-
void
Tag::Clear()
{
@@ -75,28 +69,18 @@ Tag::Clear()
tag_pool_put_item(items[i]);
tag_pool_lock.unlock();
- g_free(items);
+ delete[] items;
items = nullptr;
num_items = 0;
}
-Tag::~Tag()
-{
- tag_pool_lock.lock();
- for (int i = num_items; --i >= 0; )
- tag_pool_put_item(items[i]);
- tag_pool_lock.unlock();
-
- g_free(items);
-}
-
Tag::Tag(const Tag &other)
:time(other.time), has_playlist(other.has_playlist),
- items(nullptr),
- num_items(other.num_items)
+ 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 *
@@ -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..5d3aaad0c 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,8 +20,8 @@
#ifndef MPD_TAG_HXX
#define MPD_TAG_HXX
-#include "TagType.h"
-#include "TagItem.hxx"
+#include "TagType.h" // IWYU pragma: export
+#include "TagItem.hxx" // IWYU pragma: export
#include "Compiler.h"
#include <algorithm>
@@ -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) {}
+ 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) {
+ num_items(other.num_items), items(other.items) {
other.items = nullptr;
other.num_items = 0;
}
@@ -71,7 +71,9 @@ struct Tag {
/**
* Free the tag object and all its items.
*/
- ~Tag();
+ ~Tag() {
+ Clear();
+ }
Tag &operator=(const Tag &other) = delete;
@@ -104,24 +106,6 @@ struct Tag {
void Clear();
/**
- * Appends a new tag item.
- *
- * @param type the type of the new tag item
- * @param value the value of the tag item (not null-terminated)
- * @param len the length of #value
- */
- void AddItem(TagType type, const char *value, size_t len);
-
- /**
- * Appends a new tag item.
- *
- * @param tag the #tag object
- * @param type the type of the new tag item
- * @param value the value of the tag item (null-terminated)
- */
- void AddItem(TagType type, const char *value);
-
- /**
* Merges the data from two tags. If both tags share data for the
* same TagType, only data from "add" is used.
*
@@ -152,9 +136,6 @@ struct Tag {
*/
gcc_pure
bool HasType(TagType type) const;
-
-private:
- void AddItemInternal(TagType type, const char *value, size_t len);
};
/**
diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx
index 25e5cc24b..bc8ea2ae7 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
@@ -24,23 +24,90 @@
#include "TagString.hxx"
#include "Tag.hxx"
-#include <glib.h>
-
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
-void
-TagBuilder::Clear()
+TagBuilder::TagBuilder(const Tag &other)
+ :time(other.time), has_playlist(other.has_playlist)
{
- time = -1;
- has_playlist = false;
+ items.reserve(other.num_items);
tag_pool_lock.lock();
+ for (unsigned i = 0, n = other.num_items; i != n; ++i)
+ items.push_back(tag_pool_dup_item(other.items[i]));
+ tag_pool_lock.unlock();
+}
+
+TagBuilder::TagBuilder(Tag &&other)
+ :time(other.time), has_playlist(other.has_playlist)
+{
+ /* move all TagItem pointers from the Tag object; we don't
+ need to contact the tag pool, because all we do is move
+ references */
+ items.reserve(other.num_items);
+ std::copy_n(other.items, other.num_items, std::back_inserter(items));
+
+ /* discard the pointers from the Tag object */
+ other.num_items = 0;
+ delete[] other.items;
+ other.items = nullptr;
+}
+
+TagBuilder &
+TagBuilder::operator=(const TagBuilder &other)
+{
+ /* copy all attributes */
+ time = other.time;
+ has_playlist = other.has_playlist;
+ items = other.items;
+
+ /* increment the tag pool refcounters */
+ tag_pool_lock.lock();
for (auto i : items)
- tag_pool_put_item(i);
+ tag_pool_dup_item(i);
tag_pool_lock.unlock();
+ return *this;
+}
+
+TagBuilder &
+TagBuilder::operator=(TagBuilder &&other)
+{
+ time = other.time;
+ has_playlist = other.has_playlist;
+ items = std::move(other.items);
+
+ return *this;
+}
+
+TagBuilder &
+TagBuilder::operator=(Tag &&other)
+{
+ time = other.time;
+ has_playlist = other.has_playlist;
+
+ /* move all TagItem pointers from the Tag object; we don't
+ need to contact the tag pool, because all we do is move
+ references */
items.clear();
+ items.reserve(other.num_items);
+ std::copy_n(other.items, other.num_items, std::back_inserter(items));
+
+ /* discard the pointers from the Tag object */
+ other.num_items = 0;
+ delete[] other.items;
+ other.items = nullptr;
+
+ return *this;
+}
+
+void
+TagBuilder::Clear()
+{
+ time = -1;
+ has_playlist = false;
+ RemoveAll();
}
void
@@ -57,7 +124,7 @@ TagBuilder::Commit(Tag &tag)
object */
const unsigned n_items = items.size();
tag.num_items = n_items;
- tag.items = g_new(TagItem *, n_items);
+ tag.items = new TagItem *[n_items];
std::copy_n(items.begin(), n_items, tag.items);
items.clear();
@@ -66,14 +133,51 @@ TagBuilder::Commit(Tag &tag)
Clear();
}
-Tag *
+Tag
TagBuilder::Commit()
{
+ Tag tag;
+ Commit(tag);
+ return tag;
+}
+
+Tag *
+TagBuilder::CommitNew()
+{
Tag *tag = new Tag();
Commit(*tag);
return tag;
}
+bool
+TagBuilder::HasType(TagType type) const
+{
+ for (auto i : items)
+ if (i->type == type)
+ return true;
+
+ return false;
+}
+
+void
+TagBuilder::Complement(const Tag &other)
+{
+ if (time <= 0)
+ time = other.time;
+
+ has_playlist |= other.has_playlist;
+
+ items.reserve(items.size() + other.num_items);
+
+ tag_pool_lock.lock();
+ for (unsigned i = 0, n = other.num_items; i != n; ++i) {
+ TagItem *item = other.items[i];
+ if (!HasType(item->type))
+ items.push_back(tag_pool_dup_item(item));
+ }
+ tag_pool_lock.unlock();
+}
+
inline void
TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
{
@@ -90,7 +194,7 @@ TagBuilder::AddItemInternal(TagType type, const char *value, size_t length)
auto i = tag_pool_get_item(type, value, length);
tag_pool_lock.unlock();
- g_free(p);
+ free(p);
items.push_back(i);
}
@@ -113,3 +217,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..6de52b775 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
@@ -63,7 +63,14 @@ public:
}
TagBuilder(const TagBuilder &other) = delete;
- TagBuilder &operator=(const TagBuilder &other) = delete;
+
+ explicit TagBuilder(const Tag &other);
+ explicit TagBuilder(Tag &&other);
+
+ TagBuilder &operator=(const TagBuilder &other);
+ TagBuilder &operator=(TagBuilder &&other);
+
+ TagBuilder &operator=(Tag &&other);
/**
* Returns true if the tag contains no items. This ignores the "time"
@@ -90,11 +97,17 @@ public:
void Commit(Tag &tag);
/**
+ * Create a new #Tag instance from data in this object. This
+ * object is empty afterwards.
+ */
+ Tag Commit();
+
+ /**
* Create a new #Tag instance from data in this object. The
* returned object is owned by the caller. This object is
* empty afterwards.
*/
- Tag *Commit();
+ Tag *CommitNew();
void SetTime(int _time) {
time = _time;
@@ -109,6 +122,19 @@ public:
}
/**
+ * Checks whether the tag contains one or more items with
+ * the specified type.
+ */
+ gcc_pure
+ bool HasType(TagType type) const;
+
+ /**
+ * Copy attributes and items from the other object that do not
+ * exist in this object.
+ */
+ void Complement(const Tag &other);
+
+ /**
* Appends a new tag item.
*
* @param type the type of the new tag item
@@ -127,6 +153,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..014834b88 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
diff --git a/src/tag/TagHandler.hxx b/src/tag/TagHandler.hxx
index b8c3c6b79..6f737bf56 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
diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx
index df70a95e5..c70ec0cd9 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);
}
/**
@@ -277,19 +274,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 +290,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 +309,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 +326,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag,
if (field == nullptr)
continue;
- value = id3_field_getbinarydata(field, &length);
+ id3_length_t length;
+ const id3_byte_t *value =
+ id3_field_getbinarydata(field, &length);
if (value == nullptr || length == 0)
continue;
- char *p = g_strndup((const char *)value, length);
+ std::string p((const char *)value, length);
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_MUSICBRAINZ_TRACKID, p);
- g_free(p);
+ TAG_MUSICBRAINZ_TRACKID, p.c_str());
}
}
@@ -393,73 +381,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 +441,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 +464,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 +496,16 @@ tag_id3_riff_aiff_load(FILE *file)
/* too large, don't allocate so much memory */
return nullptr;
- id3_byte_t *buffer = (id3_byte_t *)g_malloc(size);
+ id3_byte_t *buffer = new id3_byte_t[size];
size_t ret = fread(buffer, size, 1, file);
if (ret != 1) {
LogWarning(id3_domain, "Failed to read RIFF chunk");
- g_free(buffer);
+ delete[] buffer;
return nullptr;
}
struct id3_tag *tag = id3_tag_parse(buffer, size);
- g_free(buffer);
+ delete[] buffer;
return tag;
}
diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx
index 749166116..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..477ed5f21 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
diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx
index cc28ea9a6..0b6bc956c 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,29 @@ calc_hash(TagType type, const char *p)
return hash ^ type;
}
-static inline struct slot *
+static inline constexpr TagPoolSlot *
tag_item_to_slot(TagItem *item)
{
- return (struct slot*)(((char*)item) - offsetof(struct slot, item));
+ 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 +120,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 +128,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 +139,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 +151,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 +160,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 3e8d8c1b0..0c3868eb5 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,11 +19,17 @@
#include "config.h"
#include "TagString.hxx"
+#include "util/Alloc.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_GLIB
/**
* Replace invalid sequences with the question mark.
@@ -33,7 +39,7 @@ patch_utf8(const char *src, size_t length, const gchar *end)
{
/* duplicate the string, and replace invalid bytes in that
buffer */
- char *dest = g_strdup(src);
+ char *dest = xstrdup(src);
do {
dest[end - src] = '?';
@@ -58,15 +64,20 @@ fix_utf8(const char *str, size_t length)
/* no, it's not - try to import it from ISO-Latin-1 */
temp = g_convert(str, length, "utf-8", "iso-8859-1",
nullptr, &written, nullptr);
- if (temp != nullptr)
+ if (temp != nullptr) {
/* success! */
- return temp;
+ char *p = xstrdup(temp);
+ g_free(temp);
+ return p;
+ }
/* no, still broken - there's no medication, just patch
invalid sequences */
return patch_utf8(str, length, end);
}
+#endif
+
static bool
char_is_non_printable(unsigned char ch)
{
@@ -96,7 +107,7 @@ clear_non_printable(const char *p, size_t length)
if (first == nullptr)
return nullptr;
- dest = g_strndup(p, length);
+ dest = xstrndup(p, length);
for (size_t i = first - p; i < length; ++i)
if (char_is_non_printable(dest[i]))
@@ -108,19 +119,23 @@ clear_non_printable(const char *p, size_t length)
char *
FixTagString(const char *p, size_t length)
{
- char *utf8, *cleared;
+#ifdef HAVE_GLIB
+ // TODO: implement without GLib
- utf8 = fix_utf8(p, length);
+ char *utf8 = fix_utf8(p, length);
if (utf8 != nullptr) {
p = utf8;
length = strlen(p);
}
+#endif
- cleared = clear_non_printable(p, length);
+ char *cleared = clear_non_printable(p, length);
+#ifdef HAVE_GLIB
if (cleared == nullptr)
cleared = utf8;
else
- g_free(utf8);
+ free(utf8);
+#endif
return cleared;
}
diff --git a/src/tag/TagString.hxx b/src/tag/TagString.hxx
index 79255dcd3..a1a9d9d15 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
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..098198445 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
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 fa1cf2cab..7b10de074 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 6f98d3ad0..e0d6623dd 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,10 @@ class PosixCond {
pthread_cond_t cond;
public:
- constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
+#ifndef __BIONIC__
+ constexpr
+#endif
+ PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
PosixCond(const PosixCond &other) = delete;
PosixCond &operator=(const PosixCond &other) = delete;
diff --git a/src/thread/PosixMutex.hxx b/src/thread/PosixMutex.hxx
index d50764af4..9d1674dd4 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,10 @@ class PosixMutex {
pthread_mutex_t mutex;
public:
- constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
+#ifndef __BIONIC__
+ constexpr
+#endif
+ PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
PosixMutex(const PosixMutex &other) = delete;
PosixMutex &operator=(const PosixMutex &other) = delete;
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..69172e6de
--- /dev/null
+++ b/src/util/Cast.hxx
@@ -0,0 +1,58 @@
+/*
+ * 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 CAST_HXX
+#define CAST_HXX
+
+#include <stddef.h>
+
+/**
+ * Offset the given pointer by the specified number of bytes.
+ */
+static constexpr void *
+OffsetPointer(void *p, ptrdiff_t offset)
+{
+ return (char *)p + offset;
+}
+
+template<typename T, typename U>
+static constexpr T *
+OffsetCast(U *p, ptrdiff_t offset)
+{
+ return reinterpret_cast<T *>(OffsetPointer(p, offset));
+}
+
+/**
+ * Cast the given pointer to a struct member to its parent structure.
+ */
+#define ContainerCast(p, container, attribute) \
+ OffsetCast<container, decltype(((container*)nullptr)->attribute)>\
+ ((p), -ptrdiff_t(offsetof(container, attribute)))
+
+#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..df50328b4
--- /dev/null
+++ b/src/util/DynamicFifoBuffer.hxx
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2003-2013 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FIFO_BUFFER_HPP
+#define FIFO_BUFFER_HPP
+
+#include "WritableBuffer.hxx"
+
+#include <utility>
+#include <algorithm>
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * A first-in-first-out buffer: you can append data at the end, and
+ * read data from the beginning. This class automatically shifts the
+ * buffer as needed. It is not thread safe.
+ */
+template<typename T>
+class DynamicFifoBuffer {
+public:
+ typedef size_t size_type;
+ typedef WritableBuffer<T> Range;
+ typedef typename Range::pointer_type pointer_type;
+ typedef typename Range::const_pointer_type const_pointer_type;
+
+protected:
+ size_type head, tail, capacity;
+ T *data;
+
+public:
+ explicit DynamicFifoBuffer(size_type _capacity)
+ :head(0), tail(0), capacity(_capacity),
+ data(new T[capacity]) {}
+ ~DynamicFifoBuffer() {
+ delete[] data;
+ }
+
+ DynamicFifoBuffer(const DynamicFifoBuffer &) = delete;
+
+ size_type GetCapacity() {
+ return capacity;
+ }
+
+ void Grow(size_type new_capacity) {
+ assert(new_capacity > capacity);
+
+ T *new_data = new T[new_capacity];
+ std::move(data + head, data + tail, new_data);
+ delete[] data;
+ data = new_data;
+ capacity = new_capacity;
+ tail -= head;
+ head = 0;
+ }
+
+ void Clear() {
+ head = tail = 0;
+ }
+
+ bool IsEmpty() const {
+ return head == tail;
+ }
+
+ bool IsFull() const {
+ return head == 0 && tail == capacity;
+ }
+
+ /**
+ * Prepares writing. Returns a buffer range which may be written.
+ * When you are finished, call append().
+ */
+ Range Write() {
+ Shift();
+ return Range(data + tail, capacity - tail);
+ }
+
+ /**
+ * Expands the tail of the buffer, after data has been written to
+ * the buffer returned by write().
+ */
+ void Append(size_type n) {
+ assert(tail <= capacity);
+ assert(n <= capacity);
+ assert(tail + n <= capacity);
+
+ tail += n;
+ }
+
+ void WantWrite(size_type n) {
+ if (tail + n <= capacity)
+ /* enough space after the tail */
+ return;
+
+ const size_type in_use = tail - head;
+ const size_type required_capacity = in_use + n;
+ if (capacity >= required_capacity) {
+ Shift();
+ } else {
+ size_type new_capacity = capacity;
+ do {
+ new_capacity <<= 1;
+ } while (new_capacity < required_capacity);
+
+ Grow(new_capacity);
+ }
+ }
+
+ /**
+ * Write data to the bfufer, growing it as needed. Returns a
+ * writable pointer.
+ */
+ pointer_type Write(size_type n) {
+ WantWrite(n);
+ return data + tail;
+ }
+
+ /**
+ * Append data to the buffer, growing it as needed.
+ */
+ void Append(const_pointer_type p, size_type n) {
+ std::copy_n(p, n, Write(n));
+ Append(n);
+ }
+
+ /**
+ * Return a buffer range which may be read. The buffer pointer is
+ * writable, to allow modifications while parsing.
+ */
+ Range Read() {
+ return Range(data + head, tail - head);
+ }
+
+ /**
+ * Marks a chunk as consumed.
+ */
+ void Consume(size_type n) {
+ assert(tail <= capacity);
+ assert(head <= tail);
+ assert(n <= tail);
+ assert(head + n <= tail);
+
+ head += n;
+ }
+
+ size_type Read(pointer_type p, size_type n) {
+ auto range = Read();
+ if (n > range.size)
+ n = range.size;
+ std::copy_n(range.data, n, p);
+ Consume(n);
+ return n;
+ }
+
+protected:
+ void Shift() {
+ if (head == 0)
+ return;
+
+ assert(head <= capacity);
+ assert(tail <= capacity);
+ assert(tail >= head);
+
+ std::move(data + head, data + tail, data);
+
+ tail -= head;
+ head = 0;
+ }
+};
+
+#endif
diff --git a/src/util/Error.cxx b/src/util/Error.cxx
index 5675f4d81..649276b20 100644
--- a/src/util/Error.cxx
+++ b/src/util/Error.cxx
@@ -1,31 +1,44 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Error.hxx"
#include "Domain.hxx"
+#ifdef WIN32
#include <glib.h>
+#endif
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
+#include <string.h>
const Domain errno_domain("errno");
@@ -70,7 +83,7 @@ Error::FormatPrefix(const char *fmt, ...)
void
Error::SetErrno(int e)
{
- Set(errno_domain, e, g_strerror(e));
+ Set(errno_domain, e, strerror(e));
}
void
@@ -82,7 +95,7 @@ Error::SetErrno()
void
Error::SetErrno(int e, const char *prefix)
{
- Format(errno_domain, e, "%s: %s", prefix, g_strerror(e));
+ Format(errno_domain, e, "%s: %s", prefix, strerror(e));
}
void
diff --git a/src/util/Error.hxx b/src/util/Error.hxx
index ec8867c6c..898a8f1c1 100644
--- a/src/util/Error.hxx
+++ b/src/util/Error.hxx
@@ -1,30 +1,40 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
- * http://www.musicpd.org
+ * Copyright (C) 2013 Max Kellermann <max@duempel.org>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef MPD_ERROR_HXX
-#define MPD_ERROR_HXX
+#ifndef ERROR_HXX
+#define ERROR_HXX
#include "check.h"
#include "Compiler.h"
#include <string>
-#include <algorithm>
+#include <utility>
#include <assert.h>
diff --git a/src/util/FormatString.cxx b/src/util/FormatString.cxx
index c13d0fb52..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..70d54daa2 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"
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/StringUtil.cxx b/src/util/StringUtil.cxx
index 7e295bf90..50885a34a 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,7 +21,10 @@
#include "CharUtil.hxx"
#include "ASCII.hxx"
+#include <algorithm>
+
#include <assert.h>
+#include <string.h>
const char *
strchug_fast(const char *p)
@@ -32,6 +35,50 @@ strchug_fast(const char *p)
return p;
}
+char *
+Strip(char *p)
+{
+ p = strchug_fast(p);
+
+ size_t length = strlen(p);
+ while (length > 0 && IsWhitespaceNotNull(p[length - 1]))
+ --length;
+
+ p[length] = 0;
+
+ 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..e8e3b2b5d 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,6 +22,8 @@
#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.
@@ -41,6 +43,33 @@ strchug_fast(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.
*
* @param haystack a NULL terminated list of strings
diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx
index 1c8af23fd..d839b6ce6 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
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/UriUtil.cxx b/src/util/UriUtil.cxx
index 2609db2cf..a549f7938 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)
@@ -88,6 +98,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();
diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx
index 78d0a6bff..8e00f8cd8 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);
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/win/mpd_win32_rc.rc.in b/src/win/mpd_win32_rc.rc.in
deleted file mode 100644
index a31118a0c..000000000
--- a/src/win/mpd_win32_rc.rc.in
+++ /dev/null
@@ -1,34 +0,0 @@
-#include <windows.h>
-
-#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@
-#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@"
-
-MPD_ICON ICON "@top_srcdir@/src/win/mpd.ico"
-
-1 VERSIONINFO
-FILETYPE VFT_APP
-FILEOS VOS__WINDOWS32
-PRODUCTVERSION VERSION_NUMBER
-
-FILEVERSION VERSION_NUMBER
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "040904B0"
- BEGIN
- VALUE "CompanyName", "Music Player Daemon Project"
- VALUE "ProductName", "Music Player Daemon"
- VALUE "ProductVersion", VERSION_NUMBER_STR
- VALUE "InternalName", "mpd"
- VALUE "OriginalFilename", "mpd.exe"
- VALUE "FileVersion", "@VERSION@"
- VALUE "FileDescription", "Music Player Daemon @VERSION@"
- VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project"
- END
- END
-
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x409, 1200
- END
-END
diff --git a/src/win32/Win32Main.cxx b/src/win32/Win32Main.cxx
new file mode 100644
index 000000000..75a1e9a23
--- /dev/null
+++ b/src/win32/Win32Main.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 "Main.hxx"
+
+#ifdef WIN32
+
+#include "Compiler.h"
+#include "GlobalEvents.hxx"
+#include "system/FatalError.hxx"
+
+#include <cstdlib>
+#include <atomic>
+
+#include <glib.h>
+
+#include <windows.h>
+
+static int service_argc;
+static char **service_argv;
+static char service_name[] = "";
+static std::atomic_bool running;
+static SERVICE_STATUS_HANDLE service_handle;
+
+static void WINAPI
+service_main(DWORD argc, CHAR *argv[]);
+
+static SERVICE_TABLE_ENTRY service_registry[] = {
+ {service_name, service_main},
+ {nullptr, nullptr}
+};
+
+static void
+service_notify_status(DWORD status_code)
+{
+ SERVICE_STATUS current_status;
+
+ current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING
+ ? 0
+ : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
+
+ current_status.dwCurrentState = status_code;
+ current_status.dwWin32ExitCode = NO_ERROR;
+ current_status.dwCheckPoint = 0;
+ current_status.dwWaitHint = 1000;
+
+ SetServiceStatus(service_handle, &current_status);
+}
+
+static DWORD WINAPI
+service_dispatcher(gcc_unused DWORD control, gcc_unused DWORD event_type,
+ gcc_unused void *event_data, gcc_unused void *context)
+{
+ switch (control) {
+ case SERVICE_CONTROL_SHUTDOWN:
+ case SERVICE_CONTROL_STOP:
+ GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
+ return NO_ERROR;
+ default:
+ return NO_ERROR;
+ }
+}
+
+static void WINAPI
+service_main(gcc_unused DWORD argc, gcc_unused CHAR *argv[])
+{
+ DWORD error_code;
+ gchar* error_message;
+
+ service_handle =
+ RegisterServiceCtrlHandlerEx(service_name,
+ service_dispatcher, nullptr);
+
+ if (service_handle == 0) {
+ error_code = GetLastError();
+ error_message = g_win32_error_message(error_code);
+ FormatFatalError("RegisterServiceCtrlHandlerEx() failed: %s",
+ error_message);
+ }
+
+ service_notify_status(SERVICE_START_PENDING);
+ mpd_main(service_argc, service_argv);
+ service_notify_status(SERVICE_STOPPED);
+}
+
+static BOOL WINAPI
+console_handler(DWORD event)
+{
+ switch (event) {
+ case CTRL_C_EVENT:
+ case CTRL_CLOSE_EVENT:
+ if (running.load()) {
+ // Recent msdn docs that process is terminated
+ // if this function returns TRUE.
+ // We initiate correct shutdown sequence (if possible).
+ // Once main() returns CRT will terminate our process
+ // regardless our thread is still active.
+ // If this did not happen within 3 seconds
+ // let's shutdown anyway.
+ GlobalEvents::Emit(GlobalEvents::SHUTDOWN);
+ // Under debugger it's better to wait indefinitely
+ // to allow debugging of shutdown code.
+ Sleep(IsDebuggerPresent() ? INFINITE : 3000);
+ }
+ // If we're not running main loop there is no chance for
+ // clean shutdown.
+ std::exit(EXIT_FAILURE);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+int win32_main(int argc, char *argv[])
+{
+ DWORD error_code;
+ gchar* error_message;
+
+ service_argc = argc;
+ service_argv = argv;
+
+ if (StartServiceCtrlDispatcher(service_registry))
+ return 0; /* run as service successefully */
+
+ error_code = GetLastError();
+ if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
+ /* running as console app */
+ running.store(false);
+ SetConsoleTitle("Music Player Daemon");
+ SetConsoleCtrlHandler(console_handler, TRUE);
+ return mpd_main(argc, argv);
+ }
+
+ error_message = g_win32_error_message(error_code);
+ FormatFatalError("StartServiceCtrlDispatcher() failed: %s",
+ error_message);
+}
+
+void win32_app_started()
+{
+ if (service_handle != 0)
+ service_notify_status(SERVICE_RUNNING);
+ else
+ running.store(true);
+}
+
+void win32_app_stopping()
+{
+ if (service_handle != 0)
+ service_notify_status(SERVICE_STOP_PENDING);
+ else
+ running.store(false);
+}
+
+#endif
diff --git a/src/win/mpd.ico b/src/win32/mpd.ico
index 86fd9fe43..86fd9fe43 100644
--- a/src/win/mpd.ico
+++ b/src/win32/mpd.ico
Binary files differ
diff --git a/src/win32/mpd_win32_rc.rc.in b/src/win32/mpd_win32_rc.rc.in
new file mode 100644
index 000000000..e5312dc78
--- /dev/null
+++ b/src/win32/mpd_win32_rc.rc.in
@@ -0,0 +1,34 @@
+#include <windows.h>
+
+#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@
+#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@"
+
+MPD_ICON ICON "@top_srcdir@/src/win32/mpd.ico"
+
+1 VERSIONINFO
+FILETYPE VFT_APP
+FILEOS VOS__WINDOWS32
+PRODUCTVERSION VERSION_NUMBER
+
+FILEVERSION VERSION_NUMBER
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904B0"
+ BEGIN
+ VALUE "CompanyName", "Music Player Daemon Project"
+ VALUE "ProductName", "Music Player Daemon"
+ VALUE "ProductVersion", VERSION_NUMBER_STR
+ VALUE "InternalName", "mpd"
+ VALUE "OriginalFilename", "mpd.exe"
+ VALUE "FileVersion", "@VERSION@"
+ VALUE "FileDescription", "Music Player Daemon @VERSION@"
+ VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
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..35f3dc9dc
--- /dev/null
+++ b/src/zeroconf/ZeroconfAvahi.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 "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 *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;
+
+ 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..5b5de1247
--- /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 <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() {
+ 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/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